Example #1
0
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 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 list(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
Example #2
0
        if options.encoder_params:
            video_opts += ' ' + options.encoder_params
        cmd = base_cmd + ' ' + video_opts + ' -s ' + str(
            resolutions[i][0]) + 'x' + str(
                resolutions[i][1]) + ' -f mp4 ' + temp_filename
        if options.verbose:
            print('ENCODING bitrate: %d, resolution: %dx%d' %
                  (int(bitrates[i]), resolutions[i][0], resolutions[i][1]))
        run_command(options, cmd)

        cmd = 'mp4fragment "%s" "%s"' % (temp_filename, output_filename)
        run_command(options, cmd)

        if not options.keep_files:
            os.unlink(temp_filename)


###########################
if __name__ == '__main__':
    Options = None  # global
    try:
        main()
    except Exception as err:
        if Options and Options.debug:
            raise
        else:
            PrintErrorAndExit('ERROR: %s\n' % str(err))
    finally:
        for f in TempFiles:
            os.unlink(f)
Example #3
0
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')
    if not path.exists(default_exec_dir):
        default_exec_dir = '-'

    # 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-header> argument can be either: " +
        "(1) the character '#' followed by a Widevine header encoded in Base64, or "
        +
        "(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] (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 options.exec_dir != "-":
        if not path.exists(Options.exec_dir):
            print(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'

        if options.widevine.startswith('#'):
            options.widevine = options.widevine[1:]
        else:
            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'] = bytes.fromhex(
                    options.widevine['content_id'])
            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(options, 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)