27
27
import json
28
28
import math
29
29
import operator
30
+ import struct
30
31
from functools import reduce
31
32
from subtitles import SubtitlesFile
32
33
from mp4utils import MakePsshBox ,\
@@ -336,8 +337,7 @@ def AddContentProtection(options, container, tracks, all_tracks):
336
337
container .append (xml .Comment (' Widevine ' ))
337
338
cp = xml .SubElement (container , 'ContentProtection' , schemeIdUri = WIDEVINE_SCHEME_ID_URI )
338
339
if options .widevine_header :
339
- pssh_payload = ComputeWidevineHeader (options .widevine_header , options .encryption_cenc_scheme , default_kid )
340
- pssh_box = MakePsshBox (bytes .fromhex (WIDEVINE_PSSH_SYSTEM_ID ), pssh_payload )
340
+ pssh_box = ComputeWidevinePssh (options .widevine_header , options .encryption_cenc_scheme , default_kid )
341
341
pssh_b64 = Base64Encode (pssh_box )
342
342
pssh = xml .SubElement (cp , '{' + CENC_2013_NAMESPACE + '}pssh' )
343
343
pssh .text = pssh_b64
@@ -641,8 +641,7 @@ def OutputDash(options, set_attributes, audio_sets, video_sets, subtitles_sets,
641
641
def ComputeHlsWidevineKeyLine (options , track ):
642
642
# V2 key line
643
643
kid = track .key_info ['kid' ]
644
- widevine_header = ComputeWidevineHeader (options .widevine_header , options .encryption_cenc_scheme , kid )
645
- pssh_box = MakePsshBox (bytes .fromhex (WIDEVINE_PSSH_SYSTEM_ID ), widevine_header )
644
+ pssh_box = ComputeWidevinePssh (options .widevine_header , options .encryption_cenc_scheme , kid )
646
645
key_line = 'URI="data:text/plain;base64,{}",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",KEYID=0x{},KEYFORMATVERSIONS="1"' .format (
647
646
Base64Encode (pssh_box ),
648
647
kid
@@ -1550,12 +1549,14 @@ def EncryptSources(options, media_sources):
1550
1549
1551
1550
# Widevine
1552
1551
if options .widevine_header :
1553
- widevine_header = ComputeWidevineHeader (options .widevine_header , options .encryption_cenc_scheme , default_kid )
1552
+ pssh = ComputeWidevinePssh (options .widevine_header , options .encryption_cenc_scheme , default_kid )
1553
+ pssh_payload = pssh [12 :]
1554
+ pssh_version = pssh [8 ]
1554
1555
pssh_file = tempfile .NamedTemporaryFile (dir = options .output_dir , delete = False )
1555
- pssh_file .write (widevine_header )
1556
+ pssh_file .write (pssh_payload )
1556
1557
TempFiles .append (pssh_file .name )
1557
1558
pssh_file .close () # necessary on Windows
1558
- args += ['--pssh' , WIDEVINE_PSSH_SYSTEM_ID + ':' + pssh_file .name ]
1559
+ args += ['--pssh' if pssh_version == 0 else 'pssh-v1' , WIDEVINE_PSSH_SYSTEM_ID + ':' + pssh_file .name ]
1559
1560
1560
1561
# Primetime
1561
1562
if options .primetime_metadata :
@@ -1569,6 +1570,28 @@ def EncryptSources(options, media_sources):
1569
1570
Mp4Encrypt (options , media_file , encrypted_file .name , * args )
1570
1571
media_source .filename = encrypted_file .name
1571
1572
1573
+ #############################################
1574
+ def ComputeWidevinePssh (header_spec , encryption_scheme , kid ):
1575
+ if header_spec .startswith ('#' ):
1576
+ # The header spec is a base-64 encoded precomputed byte array
1577
+ header = Base64Decode (header_spec [1 :])
1578
+ if not header :
1579
+ raise Exception ('invalid base64 encoding' )
1580
+
1581
+ # The header may be a raw header or a full PSSH box, find out which
1582
+ if len (header ) > 8 :
1583
+ (box_length , box_type ) = struct .unpack ('>I4s' , header [:8 ])
1584
+ if box_length == len (header ) and box_type == b'pssh' :
1585
+ # That looks like a valid PSSH box
1586
+ return header
1587
+
1588
+ else :
1589
+ # The header spec is a set of properties
1590
+ header = ComputeWidevineHeader (header_spec , encryption_scheme , kid )
1591
+
1592
+ # Wrap the header in a PSSH box
1593
+ return MakePsshBox (bytes .fromhex (WIDEVINE_PSSH_SYSTEM_ID ), header )
1594
+
1572
1595
#############################################
1573
1596
FileNameMap = {}
1574
1597
def MapFileName (from_name , to_name ):
@@ -1703,7 +1726,7 @@ def main():
1703
1726
parser .add_option ('' , "--widevine-header" , dest = "widevine_header" , metavar = '<widevine-header>' , default = None ,
1704
1727
help = "Add a Widevine entry in the MPD, and a Widevine PSSH box in the init segments. The use of this option implies the --widevine option. " +
1705
1728
"The <widevine-header> argument can be either: " +
1706
- "(1) the character '#' followed by a Widevine header encoded in Base64, or " +
1729
+ "(1) the character '#' followed by a Widevine header encoded in Base64 (either a complete PSSH box or just the PSSH box payload) , or " +
1707
1730
"(2) one or more <name>:<value> pair(s) (separated by '#' if more than one) specifying fields of a Widevine header (field names include 'provider' [string], 'content_id' [byte array in hex], 'policy' [string])" )
1708
1731
parser .add_option ('' , "--primetime" , dest = "primetime" , action = "store_true" , default = False ,
1709
1732
help = "Add Primetime signaling to the MPD (requires an encrypted input, or the --encryption-key option)" )
0 commit comments