Skip to content

Commit 560983e

Browse files
committed
allow full pssh boxes as input for widevine header
1 parent 4916daf commit 560983e

File tree

5 files changed

+66
-39
lines changed

5 files changed

+66
-39
lines changed

Documents/MkDocs/mkdocs.yml

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ theme:
1414

1515
docs_dir: 'src'
1616

17+
# Repo info
18+
repo_name: axiomatic-systems/Bento4
19+
repo_url: https://github.com/axiomatic-systems/Bento4
20+
1721
# Extensions
1822
markdown_extensions:
1923
- admonition

Source/C++/Core/Ap4SaizAtom.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ AP4_SaizAtom::AP4_SaizAtom(AP4_UI32 size,
8585
stream.ReadUI32(m_SampleCount);
8686
remains -= 5;
8787
if (m_DefaultSampleInfoSize == 0) {
88-
// means that the sample info entries have different sizes
88+
// means that the sample info entries have different sizes
8989
if (m_SampleCount > remains) m_SampleCount = remains; // sanity check
9090
AP4_Cardinal sample_count = m_SampleCount;
9191
m_Entries.SetItemCount(sample_count);

Source/Python/utils/mp4-dash.py

+31-8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import json
2828
import math
2929
import operator
30+
import struct
3031
from functools import reduce
3132
from subtitles import SubtitlesFile
3233
from mp4utils import MakePsshBox,\
@@ -336,8 +337,7 @@ def AddContentProtection(options, container, tracks, all_tracks):
336337
container.append(xml.Comment(' Widevine '))
337338
cp = xml.SubElement(container, 'ContentProtection', schemeIdUri=WIDEVINE_SCHEME_ID_URI)
338339
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)
341341
pssh_b64 = Base64Encode(pssh_box)
342342
pssh = xml.SubElement(cp, '{' + CENC_2013_NAMESPACE + '}pssh')
343343
pssh.text = pssh_b64
@@ -641,8 +641,7 @@ def OutputDash(options, set_attributes, audio_sets, video_sets, subtitles_sets,
641641
def ComputeHlsWidevineKeyLine(options, track):
642642
# V2 key line
643643
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)
646645
key_line = 'URI="data:text/plain;base64,{}",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",KEYID=0x{},KEYFORMATVERSIONS="1"'.format(
647646
Base64Encode(pssh_box),
648647
kid
@@ -1550,12 +1549,14 @@ def EncryptSources(options, media_sources):
15501549

15511550
# Widevine
15521551
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]
15541555
pssh_file = tempfile.NamedTemporaryFile(dir=options.output_dir, delete=False)
1555-
pssh_file.write(widevine_header)
1556+
pssh_file.write(pssh_payload)
15561557
TempFiles.append(pssh_file.name)
15571558
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]
15591560

15601561
# Primetime
15611562
if options.primetime_metadata:
@@ -1569,6 +1570,28 @@ def EncryptSources(options, media_sources):
15691570
Mp4Encrypt(options, media_file, encrypted_file.name, *args)
15701571
media_source.filename = encrypted_file.name
15711572

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+
15721595
#############################################
15731596
FileNameMap = {}
15741597
def MapFileName(from_name, to_name):
@@ -1703,7 +1726,7 @@ def main():
17031726
parser.add_option('', "--widevine-header", dest="widevine_header", metavar='<widevine-header>', default=None,
17041727
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. " +
17051728
"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 " +
17071730
"(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])")
17081731
parser.add_option('', "--primetime", dest="primetime", action="store_true", default=False,
17091732
help="Add Primetime signaling to the MPD (requires an encrypted input, or the --encryption-key option)")

Source/Python/utils/mp4utils.py

+24-30
Original file line numberDiff line numberDiff line change
@@ -1189,36 +1189,30 @@ def WidevineMakeHeader(fields):
11891189
return buffer
11901190

11911191
def ComputeWidevineHeader(header_spec, encryption_scheme, kid_hex):
1192-
# construct the base64 header
1193-
if header_spec.startswith('#'):
1194-
header_b64 = header_spec[1:]
1195-
header = Base64Decode(header_b64)
1196-
if not header:
1197-
raise Exception('invalid base64 encoding')
1198-
return header
1199-
else:
1200-
try:
1201-
pairs = header_spec.split('#')
1202-
fields = {}
1203-
for pair in pairs:
1204-
name, value = pair.split(':', 1)
1205-
fields[name] = value
1206-
except:
1207-
raise Exception('invalid syntax for argument')
1208-
1209-
protobuf_fields = [(1, 1), (2, bytes.fromhex(kid_hex))]
1210-
if 'provider' in fields:
1211-
protobuf_fields.append((3, fields['provider']))
1212-
if 'content_id' in fields:
1213-
protobuf_fields.append((4, bytes.fromhex(fields['content_id'])))
1214-
if 'policy' in fields:
1215-
protobuf_fields.append((6, fields['policy']))
1216-
1217-
if encryption_scheme != 'cenc':
1218-
four_cc = struct.unpack('>I', encryption_scheme.encode('ascii'))[0]
1219-
protobuf_fields.append((9, four_cc))
1220-
1221-
return WidevineMakeHeader(protobuf_fields)
1192+
try:
1193+
pairs = header_spec.split('#')
1194+
fields = {}
1195+
for pair in pairs:
1196+
name, value = pair.split(':', 1)
1197+
fields[name] = value
1198+
except:
1199+
raise Exception('invalid syntax for argument')
1200+
1201+
protobuf_fields = [(2, bytes.fromhex(kid_hex))]
1202+
if 'provider' in fields:
1203+
protobuf_fields.append((3, fields['provider']))
1204+
if 'content_id' in fields:
1205+
protobuf_fields.append((4, bytes.fromhex(fields['content_id'])))
1206+
if 'policy' in fields:
1207+
protobuf_fields.append((6, fields['policy']))
1208+
1209+
if encryption_scheme == 'cenc':
1210+
protobuf_fields.append((1, 1))
1211+
1212+
four_cc = struct.unpack('>I', encryption_scheme.encode('ascii'))[0]
1213+
protobuf_fields.append((9, four_cc))
1214+
1215+
return WidevineMakeHeader(protobuf_fields)
12221216

12231217
#############################################
12241218
# Module Exports

Source/Python/utils/wv-request.py

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import aes
66
import hashlib
77
import base64
8+
import struct
89
from optparse import OptionParser
910

1011
WV_DEFAULT_SERVER_URL = 'https://license.uat.widevine.com/cenc/getcontentkey'
@@ -41,6 +42,8 @@ def base64_encode(x):
4142
help="List of track types, separated by ','")
4243
parser.add_option('-l', '--policy', dest="policy", default='',
4344
help="Policy")
45+
parser.add_option('-s', '--protection-scheme', dest="protection_scheme", default='',
46+
help="Protection Scheme ('cenc', 'cbc1', 'cens', 'cbcs'), defaults to 'cenc'")
4447

4548
(options, args) = parser.parse_args()
4649
if not options.content_id:
@@ -54,6 +57,9 @@ def base64_encode(x):
5457
'policy': options.policy
5558
}
5659

60+
if options.protection_scheme:
61+
rq_payload['protection_scheme'] = struct.unpack('>I',options.protection_scheme.encode("ascii"))[0]
62+
5763
if options.track_types:
5864
rq_payload['tracks'] = [{'type': track_type} for track_type in options.track_types.split(',')]
5965
else:

0 commit comments

Comments
 (0)