Example #1
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)
Example #2
0
def main():
    # parse options
    global Options
    parser = OptionParser(
        usage="%prog [options] <media-file>",
        description="<media-file> is the path to a source video file. Version "
        + VERSION + " r" + SVN_REVISION[-5:-2])
    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('-k',
                      '--keep-files',
                      dest="keep_files",
                      action='store_true',
                      default=False,
                      help="Keep intermediate files")
    parser.add_option('-o',
                      '--output-dir',
                      dest="output_dir",
                      help="Output directory",
                      metavar="<output-dir>",
                      default='')
    parser.add_option('-b',
                      '--bitrates',
                      dest="bitrates",
                      help="Number of bitrates (default: 1)",
                      default=1,
                      type='int')
    parser.add_option(
        '-r',
        '--resolution',
        dest='resolution',
        help="Resolution of the source video (default: auto detect)")
    parser.add_option('-m',
                      '--min-video-bitrate',
                      dest='min_bitrate',
                      type='float',
                      help="Minimum bitrate (default: 500kbps)",
                      default=500.0)
    parser.add_option('-n',
                      '--max-video-bitrate',
                      dest='max_bitrate',
                      type='float',
                      help="Max Video bitrate (default: 2mbps)",
                      default=2000.0)
    parser.add_option('--audio-codec',
                      dest='audio_codec',
                      default='libfdk_aac',
                      help='Audio Codec: libfdk_aac (default) or aac')
    parser.add_option('-c',
                      '--video-codec',
                      dest='video_codec',
                      default='libx264',
                      help="Video Codec: libx264 (default) or libx265")
    parser.add_option('-a',
                      '--audio-bitrate',
                      dest='audio_bitrate',
                      type='int',
                      help="Audio bitrate (default: 128kbps)",
                      default=128)
    parser.add_option(
        '',
        '--select-streams',
        dest='select_streams',
        help=
        "Only encode these streams (comma-separated list of stream indexes or stream specifiers)"
    )
    parser.add_option('-s',
                      '--segment-size',
                      dest='segment_size',
                      type='int',
                      help="Video segment size in frames (default: 3*fps)")
    parser.add_option('-t',
                      '--text-overlay',
                      dest='text_overlay',
                      action='store_true',
                      default=False,
                      help="Add a text overlay with the bitrate")
    parser.add_option(
        '',
        '--text-overlay-font',
        dest='text_overlay_font',
        default=None,
        help="Specify a TTF font file to use for the text overlay")
    parser.add_option('-e',
                      '--encoder-params',
                      dest='encoder_params',
                      help="Extra encoder parameters")
    parser.add_option('-f',
                      '--force',
                      dest="force_output",
                      action="store_true",
                      help="Overwrite output files if they already exist",
                      default=False)
    (options, args) = parser.parse_args()
    Options = options
    if len(args) == 0:
        parser.print_help()
        sys.exit(1)

    if options.resolution:
        options.resolution = [int(x) for x in options.resolution.split('x')]
        if len(options.resolution) != 2:
            raise Exception('ERROR: invalid value for --resolution argument')

    if options.min_bitrate > options.max_bitrate:
        raise Exception('ERROR: max bitrate must be >= min bitrate')

    if options.output_dir:
        MakeNewDir(dir=options.output_dir,
                   exit_if_exists=not (options.force_output),
                   severity='ERROR')

    if options.verbose:
        print('Encoding', options.bitrates, 'bitrates, min bitrate =',
              options.min_bitrate, 'max bitrate =', options.max_bitrate)

    media_source = MediaSource(options, args[0])
    if not options.resolution:
        options.resolution = [media_source.width, media_source.height]
    if options.verbose:
        print('Media Source:', media_source)

    if not options.segment_size:
        options.segment_size = 3 * int(media_source.frame_rate + 0.5)

    if options.bitrates == 1:
        options.min_bitrate = options.max_bitrate

    (bitrates, resolutions) = compute_bitrates_and_resolutions(options)

    for i in range(options.bitrates):
        output_filename = path.join(options.output_dir,
                                    'video_%05d.mp4' % int(bitrates[i]))
        temp_filename = output_filename + '_'
        base_cmd = 'ffmpeg -i %s -strict experimental -codec:a %s -ac 2 -ab %dk -preset slow -map_metadata -1 -codec:v %s' % (
            quote(args[0]), options.audio_codec, options.audio_bitrate,
            options.video_codec)
        if options.video_codec == 'libx264':
            base_cmd += ' -profile:v baseline'
        if options.text_overlay:
            if not options.text_overlay_font:
                font_file = "/Library/Fonts/Courier New.ttf"
                if path.exists(font_file):
                    options.text_overlay_font = font_file
                else:
                    raise Exception(
                        'ERROR: no default font file, please use the --text-overlay-font option'
                    )
            if not path.exists(options.text_overlay_font):
                raise Exception('ERROR: font file "' +
                                options.text_overlay_font + '" does not exist')
            base_cmd += ' -vf "drawtext=fontfile=' + options.text_overlay_font + ': text=' + str(
                int(bitrates[i])
            ) + 'kbps ' + str(resolutions[i][0]) + '*' + str(
                resolutions[i]
                [1]) + ': fontsize=50:  x=(w)/8: y=h-(2*lh): fontcolor=white:"'
        if options.select_streams:
            specifiers = options.select_streams.split(',')
            for specifier in specifiers:
                base_cmd += ' -map 0:' + specifier
        else:
            base_cmd += ' -map 0'
        if not options.debug:
            base_cmd += ' -v quiet'
        if options.force_output:
            base_cmd += ' -y'

        #x264_opts = "-x264opts keyint=%d:min-keyint=%d:scenecut=0:rc-lookahead=%d" % (options.segment_size, options.segment_size, options.segment_size)
        #video_opts = "-g %d" % (options.segment_size)
        video_opts = "-force_key_frames 'expr:eq(mod(n,%d),0)'" % (
            options.segment_size)
        video_opts += " -bufsize %dk -maxrate %dk" % (bitrates[i],
                                                      int(bitrates[i] * 1.5))
        if options.video_codec == 'libx264':
            video_opts += " -x264opts rc-lookahead=%d" % (options.segment_size)
        elif options.video_codec == 'libx265':
            video_opts += ' -x265-params "no-open-gop=1:keyint=%d:no-scenecut=1:profile=main"' % (
                options.segment_size)
        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)
Example #3
0
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_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(list(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),
                           'w',
                           newline='\r\n')
    master_playlist.write('#EXTM3U\n')
    master_playlist.write('# Created with Bento4 mp4-hls.py version ' +
                          VERSION + 'r' + SDK_REVISION + '\n')

    if options.hls_version >= 4:
        master_playlist.write('\n')
        master_playlist.write('#EXT-X-VERSION:' + str(options.hls_version) +
                              '\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 + '\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('\n')
        master_playlist.write('# Subtitles\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"\n'
                % (subtitles_file.language_name, subtitles_file.language,
                   relative_url))

    # process audio sources
    audio_groups = []
    if len(audio_tracks):
        master_playlist.write('\n')
        master_playlist.write('# Audio\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"\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)))
            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('\n')
    master_playlist.write('# Media Playlists\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 subtitles_files:
                ext_x_stream_inf += ',SUBTITLES="subtitles"'

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

    # write the I-FRAME playlist info
    if not audio_only and options.hls_version >= 4:
        master_playlist.write('\n')
        master_playlist.write('# I-Frame Playlists\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 + '\n')