%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/waritko/build/Bento4/Source/Python/utils/
Upload File :
Create Path :
Current File : //home/waritko/build/Bento4/Source/Python/utils/mp4-hls.py

#!/usr/bin/env python

__author__    = 'Gilles Boccon-Gibod (bok@bok.net)'
__copyright__ = 'Copyright 2011-2015 Axiomatic Systems, LLC.'

###
# NOTE: this script needs Bento4 command line binaries to run
# You must place the 'mp4info' 'mp4dump', and 'mp42hls' binaries
# in a directory named 'bin/<platform>' at the same level as where
# this script is.
# <platform> depends on the platform you're running on:
# Mac OSX   --> platform = macosx
# Linux x86 --> platform = linux-x86
# Windows   --> platform = win32

from optparse import OptionParser
import shutil
import xml.etree.ElementTree as xml
from xml.dom.minidom import parseString
import tempfile
import fractions
import re
import platform
import sys
from mp4utils import *
from subtitles import *

# setup main options
VERSION = "1.1.0"
SDK_REVISION = '623'
SCRIPT_PATH = path.abspath(path.dirname(__file__))
sys.path += [SCRIPT_PATH]

#############################################
def CreateSubtitlesPlaylist(playlist_filename, webvtt_filename, duration):
    playlist = open(playlist_filename, 'wb+')
    playlist.write('#EXTM3U\r\n')
    playlist.write('#EXT-X-TARGETDURATION:%d\r\n' % (duration))
    playlist.write('#EXT-X-VERSION:3\r\n')
    playlist.write('#EXT-X-MEDIA-SEQUENCE:0\r\n')
    playlist.write('#EXT-X-PLAYLIST-TYPE:VOD\r\n')
    playlist.write('#EXTINF:%d,\r\n' % (duration))
    playlist.write(webvtt_filename+'\r\n')
    playlist.write('#EXT-X-ENDLIST\r\n')


#############################################
def ComputeCodecName(codec_family):
    name = codec_family
    if codec_family == 'mp4a':
        name = 'aac'
    elif codec_family == 'ac-3':
        name = 'ac3'
    elif codec_family == 'ec-3':
        name = 'ec3'
    return name

#############################################
def SplitArgs(args):
    try:
        pairs = args.split('#')
        fields = {}
        for pair in pairs:
            name, value = pair.split(':', 1)
            fields[name] = value
        return fields
    except:
        raise Exception('invalid syntax for argument')

#############################################
def ComputeWidevineKeyLine(params):
    json_param = '{ "provider": "%(provider)s", "content_id": "%(content_id)s", "key_ids": ["%(kid)s"] }' % params
    key_line   = 'URI="data:text/plain;base64,'+json_param.encode('base64').replace('\n','')+'",KEYFORMAT="com.widevine",KEYFORMATVERSIONS="1"'

    return key_line

#############################################
def ComputeFairplayKeyLine(params):
    # start with a '!' to specify we want to skip the IV (since it is not needed on the key line for Fairplay)
    return '!URI="'+params['uri']+'",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1"'

#############################################
def AnalyzeSources(options, media_sources):
    # parse the media files
    mp4_files = {}
    for media_source in media_sources:
        if media_source.format != 'mp4': continue

        media_file = media_source.filename

        # check if we have already parsed this file
        if media_file in mp4_files:
            media_source.mp4_file = mp4_files[media_file]
            continue

        # parse the file
        if not os.path.exists(media_file):
            PrintErrorAndExit('ERROR: media file ' + media_file + ' does not exist')

        # get the file info
        print 'Parsing media file', media_file
        mp4_file = Mp4File(Options, media_source)
        media_source.mp4_file = mp4_file

        # remember we have parsed this file
        mp4_files[media_file] = mp4_file

    # analyze the media sources
    for media_source in media_sources:
        track_id       = media_source.spec['track']
        track_type     = media_source.spec['type']
        track_language = media_source.spec['language']
        tracks         = []

        if media_source.format != 'mp4':
            if track_id or track_type:
                PrintErrorAndExit('ERROR: track ID and track type selections only apply to MP4 media sources')
            continue

        if track_id and track_type:
            PrintErrorAndExit('ERROR: track ID and track type selections are mutually exclusive')

        if track_id:
            tracks = [media_source.mp4_file.find_track_by_id(track_id)]
            if not tracks:
                PrintErrorAndExit('ERROR: track id not found for media file '+media_source.name)

        if track_type:
            tracks = media_source.mp4_file.find_tracks_by_type(track_type)
            if not tracks:
                PrintErrorAndExit('ERROR: no ' + track_type + ' found for media file '+media_source.name)

        if not tracks:
            for track in media_source.mp4_file.tracks.values():
                language = LanguageCodeMap.get(track.language, track.language)
                if track_language and track_language != language and track_language != track.language:
                    continue
                tracks.append(track)

        # remember if this media source has a video or audio track
        for track in tracks:
            if track.type == 'video':
                media_source.has_video = True
            if track.type == 'audio':
                media_source.has_audio = True

        media_source.tracks = tracks

#############################################
def SelectAudioTracks(options, media_sources):
    # select tracks grouped by codec
    audio_tracks = {}
    for media_source in media_sources:

        # pre-process the track metadata
        for track in media_source.tracks:
            # track group
            track.group_id = ComputeCodecName(track.codec_family)

            # track language
            remap_language = media_source.spec.get('+language')
            if remap_language:
                track.language = remap_language
            language_name = LanguageNames.get(track.language, track.language)
            track.language_name = media_source.spec.get('+language_name', language_name).decode('utf-8')

        # process audio tracks
        for track in [t for t in media_source.tracks if t.type == 'audio']:
            group_id = track.group_id
            group = audio_tracks.get(group_id, [])
            audio_tracks[group_id] = group
            if len([x for x in group if x.language == track.language]):
                continue # only accept one track for each language per group
            group.append(track)

    return audio_tracks

#############################################
def ProcessSource(options, media_info, out_dir):
    if options.verbose:
        print 'Processing', media_info['source'].filename

    file_extension = media_info.get('file_extension', 'ts')

    kwargs = {
        'index_filename':            path.join(out_dir, options.media_playlist_name),
        'segment_filename_template': path.join(out_dir, 'segment-%d.'+file_extension),
        'segment_url_template':      'segment-%d.'+file_extension,
        'show_info':                 True
    }

    if options.base_url != "":
        kwargs["segment_url_template"] = options.base_url+media_info["dir"]+'/'+'segment-%d.'+file_extension

    if options.hls_version != 3:
        kwargs['hls_version'] = str(options.hls_version)

    if options.hls_version >= 4:
        kwargs['iframe_index_filename'] = path.join(out_dir, options.iframe_playlist_name)

    if options.output_single_file:
        kwargs['segment_filename_template'] = path.join(out_dir, 'media.'+file_extension)
        kwargs['segment_url_template']      = 'media.'+file_extension
        kwargs['output_single_file']        = True

    if 'audio_format' in media_info and media_info.get('audio_track_id') != 0:
        kwargs['audio_format'] = media_info['audio_format']

    for option in ['encryption_mode', 'encryption_key', 'encryption_iv_mode', 'encryption_key_uri', 'encryption_key_format', 'encryption_key_format_versions']:
        if getattr(options, option):
            kwargs[option] = getattr(options, option)

    key_lines = []

    # Fairplay
    if options.fairplay:
        key_lines.append(ComputeFairplayKeyLine(options.fairplay))

    # Widevine
    if options.widevine:
        key_lines.append(ComputeWidevineKeyLine(options.widevine))

    if len(key_lines):
        kwargs['encryption_key_line'] = key_lines

    # deal with track IDs
    if 'audio_track_id' in media_info:
        kwargs['audio_track_id'] = str(media_info['audio_track_id'])
    if 'video_track_id' in media_info:
        kwargs['video_track_id'] = str(media_info['video_track_id'])

    # other options
    if options.segment_duration:
        kwargs['segment_duration'] = options.segment_duration

    # convert to HLS/TS
    json_info = Mp42Hls(options,
                        media_info['source'].filename,
                        **kwargs)

    media_info['info'] = json.loads(json_info, strict=False)
    if options.verbose:
        print json_info

    # output the encryption key if needed
    if options.output_encryption_key:
        open(path.join(out_dir, 'key.bin'), 'wb+').write(options.encryption_key.decode('hex')[:16])

#############################################
def OutputHls(options, media_sources):
    mp4_sources = [media_source for media_source in media_sources if media_source.format == 'mp4']

    # analyze the media sources
    AnalyzeSources(options, media_sources)

    # select audio tracks
    audio_media = []
    audio_tracks = SelectAudioTracks(options, [media_source for media_source in mp4_sources if not media_source.spec.get('+audio_fallback')])

    # check if this is an audio-only presentation
    audio_only = True
    for media_source in mp4_sources:
        if media_source.has_video:
            audio_only = False
            break

    # check if the video has muxed audio
    video_has_muxed_audio = False
    for media_source in mp4_sources:
        if media_source.has_video and media_source.has_audio:
            video_has_muxed_audio = True
            break

    # audio-only presentations don't need alternate audio tracks
    if audio_only:
        audio_tracks = {}

    # we only need alternate audio tracks if there are more than one or if the audio and video are not muxed
    if video_has_muxed_audio and not audio_only and len(audio_tracks) == 1 and len(audio_tracks.values()[0]) == 1:
        audio_tracks = {}

    # process main media sources
    total_duration = 0
    main_media = []
    for media_source in mp4_sources:
        if not audio_only and not media_source.spec.get('+audio_fallback') and not media_source.has_video:
            continue
        media_index = 1+len(main_media)
        media_info = {
            'source':      media_source,
            'dir':         'media-'+str(media_index)
        }
        if audio_only:
            media_info['video_track_id'] = 0
            if options.audio_format == 'packed':
                source_audio_tracks = media_source.mp4_file.find_tracks_by_type('audio')
                if len(source_audio_tracks):
                    media_info['audio_format']   = options.audio_format
                    if options.audio_format == 'packed':
                        media_info['file_extension'] = ComputeCodecName(source_audio_tracks[0].codec_family)

        # no audio if there's a type filter for video
        if media_source.spec.get('type') == 'video':
            media_info['audio_track_id'] = 0

        # deal with audio-fallback streams
        if media_source.spec.get('+audio_fallback') == 'yes':
            media_info['video_track_id'] = 0

        # process the source
        out_dir = path.join(options.output_dir, media_info['dir'])
        MakeNewDir(out_dir)
        ProcessSource(options, media_info, out_dir)

        # update the duration
        duration_s = int(media_info['info']['stats']['duration'])
        if duration_s > total_duration:
            total_duration = duration_s

        main_media.append(media_info)

    # process audio tracks
    if len(audio_tracks):
        MakeNewDir(path.join(options.output_dir, 'audio'))
    for group_id in audio_tracks:
        group = audio_tracks[group_id]
        MakeNewDir(path.join(options.output_dir, 'audio', group_id))
        for audio_track in group:
            audio_track.media_info = {
                'source':         audio_track.parent.media_source,
                'audio_format':   options.audio_format,
                'dir':            'audio/'+group_id+'/'+audio_track.language,
                'language':       audio_track.language,
                'language_name':  audio_track.language_name,
                'audio_track_id': audio_track.id,
                'video_track_id': 0
            }
            if options.audio_format == 'packed':
                audio_track.media_info['file_extension'] = ComputeCodecName(audio_track.codec_family)

            # process the source
            out_dir = path.join(options.output_dir, 'audio', group_id, audio_track.language)
            MakeNewDir(out_dir)
            ProcessSource(options, audio_track.media_info, out_dir)

    # start the master playlist
    master_playlist = open(path.join(options.output_dir, options.master_playlist_name), "wb+")
    master_playlist.write("#EXTM3U\r\n")
    master_playlist.write('# Created with Bento4 mp4-hls.py version '+VERSION+'r'+SDK_REVISION+'\r\n')

    if options.hls_version >= 4:
        master_playlist.write('\r\n')
        master_playlist.write('#EXT-X-VERSION:'+str(options.hls_version)+'\r\n')

    # optional session key
    if options.signal_session_key:
        ext_x_session_key_line = '#EXT-X-SESSION-KEY:METHOD='+options.encryption_mode+',URI="'+options.encryption_key_uri+'"'
        if options.encryption_key_format:
            ext_x_session_key_line += ',KEYFORMAT="'+options.encryption_key_format+'"'
        if options.encryption_key_format_versions:
            ext_x_session_key_line += ',KEYFORMATVERSIONS="'+options.encryption_key_format_versions+'"'
        master_playlist.write(ext_x_session_key_line+'\r\n')

    # process subtitles sources
    subtitles_files = [SubtitlesFile(options, media_source) for media_source in media_sources if media_source.format in ['ttml', 'webvtt']]
    if len(subtitles_files):
        master_playlist.write('\r\n')
        master_playlist.write('# Subtitles\r\n')
        MakeNewDir(path.join(options.output_dir, 'subtitles'))
        for subtitles_file in subtitles_files:
            out_dir = path.join(options.output_dir, 'subtitles', subtitles_file.language)
            MakeNewDir(out_dir)
            media_filename = path.join(out_dir, subtitles_file.media_name)
            shutil.copyfile(subtitles_file.media_source.filename, media_filename)
            relative_url = 'subtitles/'+subtitles_file.language+'/subtitles.m3u8'
            playlist_filename = path.join(out_dir, 'subtitles.m3u8')
            CreateSubtitlesPlaylist(playlist_filename, subtitles_file.media_name, total_duration)

            master_playlist.write('#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subtitles",NAME="%s",LANGUAGE="%s",URI="%s"\r\n' % (subtitles_file.language_name, subtitles_file.language, relative_url))

    # process audio sources
    audio_groups = []
    if len(audio_tracks):
        master_playlist.write('\r\n')
        master_playlist.write('# Audio\r\n')
        for group_id in audio_tracks:
            group = audio_tracks[group_id]
            group_name = 'audio_'+group_id
            group_codec = group[0].codec
            default = True
            group_avg_segment_bitrate = 0
            group_max_segment_bitrate = 0
            for audio_track in group:
                avg_segment_bitrate = int(audio_track.media_info['info']['stats']['avg_segment_bitrate'])
                max_segment_bitrate = int(audio_track.media_info['info']['stats']['max_segment_bitrate'])
                if avg_segment_bitrate > group_avg_segment_bitrate:
                    group_avg_segment_bitrate = avg_segment_bitrate
                if max_segment_bitrate > group_max_segment_bitrate:
                    group_max_segment_bitrate = max_segment_bitrate
                extra_info = 'AUTOSELECT=YES,'
                if default:
                    extra_info += 'DEFAULT=YES,'
                    default = False
                master_playlist.write(('#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="%s",NAME="%s",LANGUAGE="%s",%sURI="%s"\r\n' % (
                                      group_name,
                                      audio_track.media_info['language_name'],
                                      audio_track.media_info['language'],
                                      extra_info,
                                      options.base_url+audio_track.media_info['dir']+'/'+options.media_playlist_name)).encode('utf-8'))
            audio_groups.append({
                'name':                group_name,
                'codec':               group_codec,
                'avg_segment_bitrate': group_avg_segment_bitrate,
                'max_segment_bitrate': group_max_segment_bitrate
            })

        if options.debug:
            print 'Audio Groups:'
            print audio_groups

    else:
        audio_groups = [{
            'name':                None,
            'codec':               None,
            'avg_segment_bitrate': 0,
            'max_segment_bitrate': 0
        }]

    # media playlists
    master_playlist.write('\r\n')
    master_playlist.write('# Media Playlists\r\n')
    for media in main_media:
        media_info = media['info']

        for group_info in audio_groups:
            group_name  = group_info['name']
            group_codec = group_info['codec']

            # stream inf
            codecs = []
            if 'video' in media_info:
                codecs.append(media_info['video']['codec'])
            if 'audio' in media_info:
                codecs.append(media_info['audio']['codec'])
            elif group_name and group_codec:
                codecs.append(group_codec)

            ext_x_stream_inf = '#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=%d,BANDWIDTH=%d,CODECS="%s"' % (
                                int(media_info['stats']['avg_segment_bitrate'])+group_info['avg_segment_bitrate'],
                                int(media_info['stats']['max_segment_bitrate'])+group_info['max_segment_bitrate'],
                                ','.join(codecs))
            if 'video' in media_info:
                ext_x_stream_inf += ',RESOLUTION='+str(int(media_info['video']['width']))+'x'+str(int(media_info['video']['height']))

            # audio info
            if group_name:
                ext_x_stream_inf += ',AUDIO="'+group_name+'"'

            # subtitles info
            if len(subtitles_files):
                ext_x_stream_inf += ',SUBTITLES="subtitles"'

            master_playlist.write(ext_x_stream_inf+'\r\n')
            master_playlist.write(options.base_url+media['dir']+'/'+options.media_playlist_name+'\r\n')

    # write the I-FRAME playlist info
    if not audio_only and options.hls_version >= 4:
        master_playlist.write('\r\n')
        master_playlist.write('# I-Frame Playlists\r\n')
        for media in main_media:
            media_info = media['info']
            if not 'video' in media_info: continue
            ext_x_i_frame_stream_inf = '#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=%d,BANDWIDTH=%d,CODECS="%s",RESOLUTION=%dx%d,URI="%s"' % (
                                        int(media_info['stats']['avg_iframe_bitrate']),
                                        int(media_info['stats']['max_iframe_bitrate']),
                                        media_info['video']['codec'],
                                        int(media_info['video']['width']),
                                        int(media_info['video']['height']),
                                        options.base_url+media['dir']+'/'+options.iframe_playlist_name)
            master_playlist.write(ext_x_i_frame_stream_inf+'\r\n')

#############################################
Options = None
def main():
    # determine the platform binary name
    host_platform = ''
    if platform.system() == 'Linux':
        if platform.processor() == 'x86_64':
            host_platform = 'linux-x86_64'
        else:
            host_platform = 'linux-x86'
    elif platform.system() == 'Darwin':
        host_platform = 'macosx'
    elif platform.system() == 'Windows':
        host_platform = 'win32'
    default_exec_dir = path.join(SCRIPT_PATH, 'bin', host_platform)
    if not path.exists(default_exec_dir):
        default_exec_dir = path.join(SCRIPT_PATH, 'bin')
    if not path.exists(default_exec_dir):
        default_exec_dir = path.join(SCRIPT_PATH, '..', 'bin')

    # parse options
    parser = OptionParser(usage="%prog [options] <media-file> [<media-file> ...]",
                          description="Each <media-file> is the path to an MP4 file, optionally prefixed with a stream selector delimited by [ and ]. The same input MP4 file may be repeated, provided that the stream selector prefixes select different streams. Version " + VERSION + " r" + SDK_REVISION)
    parser.add_option('-v', '--verbose', dest="verbose", action='store_true', default=False,
                      help="Be verbose")
    parser.add_option('-d', '--debug', dest="debug", action='store_true', default=False,
                      help="Print out debugging information")
    parser.add_option('-o', '--output-dir', dest="output_dir",
                      help="Output directory", metavar="<output-dir>", default='output')
    parser.add_option('-f', '--force', dest="force_output", action="store_true", default=False,
                      help="Allow output to an existing directory")
    parser.add_option('', '--hls-version', dest="hls_version", type="int", metavar="<version>", default=4,
                      help="HLS Version (default: 4)")
    parser.add_option('', '--master-playlist-name', dest="master_playlist_name", metavar="<filename>", default='master.m3u8',
                      help="Master Playlist name")
    parser.add_option('', '--media-playlist-name', dest="media_playlist_name", metavar="<name>", default='stream.m3u8',
                      help="Media Playlist name")
    parser.add_option('', '--iframe-playlist-name', dest="iframe_playlist_name", metavar="<name>", default='iframes.m3u8',
                      help="I-frame Playlist name")
    parser.add_option('', '--output-single-file', dest="output_single_file", action='store_true', default=False,
                      help="Store segment data in a single output file per input file")
    parser.add_option('', '--audio-format', dest="audio_format", default='packed',
                      help="Format for audio segments (packed or ts) (default: packed)")
    parser.add_option('', '--segment-duration', dest="segment_duration",
                      help="Segment duration (default: 6)")
    parser.add_option('', '--encryption-mode', dest="encryption_mode", metavar="<mode>",
                      help="Encryption mode (only used when --encryption-key is specified). AES-128 or SAMPLE-AES (default: AES-128)")
    parser.add_option('', '--encryption-key', dest="encryption_key", metavar="<key>",
                      help="Encryption key in hexadecimal (default: no encryption)")
    parser.add_option('', '--encryption-iv-mode', dest="encryption_iv_mode", metavar="<mode>",
                      help="Encryption IV mode: 'sequence', 'random' or 'fps' (Fairplay Streaming) (default: sequence). When the mode is 'fps', the encryption key must be 32 bytes: 16 bytes for the key followed by 16 bytes for the IV.")
    parser.add_option('', '--encryption-key-uri', dest="encryption_key_uri", metavar="<uri>", default="key.bin",
                      help="Encryption key URI (may be a relative or absolute URI). (default: key.bin)")
    parser.add_option('', '--encryption-key-format', dest="encryption_key_format", metavar="<format>",
                      help="Encryption key format. (default: 'identity')")
    parser.add_option('', '--encryption-key-format-versions', dest="encryption_key_format_versions", metavar="<versions>",
                      help="Encryption key format versions.")
    parser.add_option('', '--signal-session-key', dest='signal_session_key', action='store_true', default=False,
                      help="Signal an #EXT-X-SESSION-KEY tag in the master playlist")
    parser.add_option('', '--fairplay', dest="fairplay", metavar="<fairplay-parameters>", help="Enable Fairplay Key Delivery. The <fairplay-parameters> argument is one or more <name>:<value> pair(s) (separated by '#' if more than one). Names include 'uri' [string] (required)")
    parser.add_option('', '--widevine', dest="widevine", metavar="<widevine-parameters>", help="Enable Widevine Key Delivery. The <widevine-parameters> argument is one or more <name>:<value> pair(s) (separated by '#' if more than one). Names include 'provider' [string] (required), 'content_id' [byte array in hex] (optional), 'kid' [16-byte array in hex] (required)")
    parser.add_option('', '--output-encryption-key', dest="output_encryption_key", action="store_true", default=False,
                      help="Output the encryption key to a file (default: don't output the key). This option is only valid when the encryption key format is 'identity'")
    parser.add_option('', "--exec-dir", metavar="<exec_dir>", dest="exec_dir", default=default_exec_dir,
                      help="Directory where the Bento4 executables are located")
    parser.add_option('', "--base-url", metavar="<base_url>", dest="base_url", default="",
                      help="The base URL for the Media Playlists and TS files listed in the playlists. This is the prefix for the files.")
    (options, args) = parser.parse_args()
    if len(args) == 0:
        parser.print_help()
        sys.exit(1)
    global Options
    Options = options

    # set some mandatory options that utils rely upon
    options.min_buffer_time = 0.0

    if not path.exists(Options.exec_dir):
        PrintErrorAndExit('Executable directory does not exist ('+Options.exec_dir+'), use --exec-dir')

    # check options
    if options.output_encryption_key:
        if options.encryption_key_uri != "key.bin":
            sys.stderr.write("WARNING: the encryption key will not be output because a non-default key URI was specified\n")
            options.output_encryption_key = False
        if not options.encryption_key:
            sys.stderr.write("ERROR: --output-encryption-key requires --encryption-key to be specified\n")
            sys.exit(1)
        if options.encryption_key_format != None and options.encryption_key_format != 'identity':
            sys.stderr.write("ERROR: --output-encryption-key requires --encryption-key-format to be omitted or set to 'identity'\n")
            sys.exit(1)

    # Fairplay option
    if options.fairplay:
        if not options.encryption_key_format:
            options.encryption_key_format = 'com.apple.streamingkeydelivery'
        if not options.encryption_key_format_versions:
            options.encryption_key_format_versions = '1'

        if options.encryption_iv_mode:
            if options.encryption_iv_mode != 'fps':
                sys.stderr.write("ERROR: --fairplay requires --encryption-iv-mode to be 'fps'\n")
                sys.exit(1)
        else:
            options.encryption_iv_mode = 'fps'
        if not options.encryption_key:
            sys.stderr.write("ERROR: --fairplay requires --encryption-key to be specified\n")
            sys.exit(1)
        if options.encryption_mode:
            if options.encryption_mode != 'SAMPLE-AES':
                sys.stderr.write('ERROR: --fairplay option incompatible with '+options.encryption_mode+' encryption mode\n')
                sys.exit(1)
        else:
            options.encryption_mode = 'SAMPLE-AES'
        options.fairplay = SplitArgs(options.fairplay)
        if 'uri' not in options.fairplay:
            sys.stderr.write('ERROR: --fairplay option requires a "uri" parameter (ex: skd://xxx)\n')
            sys.exit(1)

        options.signal_session_key = True

    # Widevine option
    if options.widevine:
        if not options.encryption_key:
            sys.stderr.write("ERROR: --widevine requires --encryption-key to be specified\n")
            sys.exit(1)
        if options.encryption_mode:
            if options.encryption_mode != 'SAMPLE-AES':
                sys.stderr.write('ERROR: --widevine option incompatible with '+options.encryption_mode+' encryption mode\n')
                sys.exit(1)
        else:
            options.encryption_mode = 'SAMPLE-AES'
        options.widevine = SplitArgs(options.widevine)
        if 'kid' not in options.widevine:
            sys.stderr.write('ERROR: --widevine option requires a "kid" parameter\n')
            sys.exit(1)
        if len(options.widevine['kid']) != 32:
            sys.stderr.write('ERROR: --widevine option "kid" must be 32 hex characters\n')
            sys.exit(1)
        if 'provider' not in options.widevine:
            sys.stderr.write('ERROR: --widevine option requires a "provider" parameter\n')
            sys.exit(1)
        if 'content_id' in options.widevine:
            options.widevine['content_id'] = options.widevine['content_id'].decode('hex')
        else:
            options.widevine['content_id'] = '*'

    # defaults
    if options.encryption_key and not options.encryption_mode:
        options.encryption_mode = 'AES-128'

    if options.encryption_mode == 'SAMPLE-AES':
        options.hls_version = 5

    # parse media sources syntax
    media_sources = [MediaSource(source) for source in args]
    for media_source in media_sources:
        media_source.has_audio  = False
        media_source.has_video  = False

    # create the output directory
    severity = 'ERROR'
    if options.force_output: severity = None
    MakeNewDir(dir=options.output_dir, exit_if_exists = not options.force_output, severity=severity)

    # output the media playlists
    OutputHls(options, media_sources)

###########################
if sys.version_info[0] != 2:
    sys.stderr.write("ERROR: This tool must be run with Python 2.x\n")
    sys.stderr.write("You are running Python version: "+sys.version+"\n")
    exit(1)

if __name__ == '__main__':
    try:
        main()
    except Exception, err:
        if Options and Options.debug:
            raise
        else:
            PrintErrorAndExit('ERROR: %s\n' % str(err))

Zerion Mini Shell 1.0