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
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)
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)