Ejemplo n.º 1
0
def convert_file(filename, codec, force):
    if force:
        add_y = " -y "
    else:
        add_y = ""
    cmd = "ffmpeg {add_y} -i {filename} -vcodec copy -acodec copy".format(
        **locals())
    # copy vp9 stream to vfi container -> nice to parse
    if codec == 'vp9':
        conv_filename = "".join([filename, "_tmp.ivf"])
        cmd += " {conv_filename}".format(**locals())
    # create file with raw h264 stream formatted according to annex-b
    elif codec == 'h264':
        conv_filename = "".join([filename, "_tmp.h264"])
        cmd += " -bsf:v h264_mp4toannexb {conv_filename}".format(**locals())
    # create file with raw h265 stream formatted according to annex-b
    else:
        conv_filename = "".join([filename, "_tmp.h265"])
        cmd += " -bsf:v hevc_mp4toannexb {conv_filename}".format(**locals())
    # if there is already a temporary file, use this if -f is not specified
    if os.path.isfile(conv_filename) and not force:
        return conv_filename
    cmd_utils.run_command(
        cmd, "converting {filename} to {conv_filename}".format(**locals()))

    return conv_filename
Ejemplo n.º 2
0
def get_src_info(src):
    """
    Get info about the SRC, as shown by ffprobe.
    Possible return keys, including example output:
        - codec_name: "h264"
        - codec_long_name: "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"
        - profile: "High 4:4:4 Predictive"
        - codec_type: "video"
        - codec_time_base: "1/120"
        - codec_tag_string: "avc1"
        - codec_tag: "0x31637661"
        - width: 3840
        - height: 2160
        - coded_width: 3840
        - coded_height: 2160
        - has_b_frames: 0
        - sample_aspect_ratio: "1:1"
        - display_aspect_ratio: "16:9"
        - pix_fmt: "yuv444p"
        - level: 52
        - chroma_location: "left"
        - refs: 1
        - is_avc: "true"
        - nal_length_size: "4"
        - r_frame_rate: "60/1"
        - avg_frame_rate: "60/1"
        - time_base: "1/15360"
        - start_pts: 0
        - start_time: "0.000000"
        - duration_ts: 153600
        - duration: "10.000000"
        - bit_rate: "1569904"
        - bits_per_raw_sample: "8"
        - nb_frames: "600"
    """
    input_file = src.file_path

    cmd = "ffprobe -loglevel error -select_streams v -show_streams -of json '" + input_file + "'"
    if not os.path.isfile(src.info_path):
        stdout, _ = cmd_utils.run_command(cmd,
                                          name="get SRC info for " + str(src))
        info = json.loads(stdout)
        returndata = info["streams"][0]

        videosize = get_stream_size(src)
        audiosize = get_stream_size(src, 'audio')

        info_to_dump = {}
        info_to_dump['md5sum'] = '-'
        info_to_dump['get_stream_size'] = {"v": videosize, "a": audiosize}
        info_to_dump['get_src_info'] = returndata

        with open(src.info_path, 'w') as outfile:
            yaml.dump(info_to_dump, outfile, default_flow_style=False)
    else:
        with open(src.info_path) as f_in:
            ydata = yaml.load(f_in, Loader=yaml.FullLoader)
            returndata = ydata['get_src_info']
    return returndata
def get_processing_chain_version():
    processing_chain_dir = get_processing_chain_dir()
    git_version, _ = cmd_utils.run_command('cd "' + processing_chain_dir +
                                           '" && git describe --always')
    with open(os.path.join(get_processing_chain_dir(), 'VERSION'),
              'r') as version_f:
        major_version = version_f.readlines()[0].strip()
    version = git_version.strip() + " v" + major_version
    return version
Ejemplo n.º 4
0
def get_stream_size(segment, stream_type="video"):
    """
    Return the video stream size in Bytes, as determined by summing up the individual
    frame size.

    stream_type: either "video" or "audio"
    """
    switch = "v" if stream_type == "video" else "a"
    cmd = "ffprobe -loglevel error -select_streams " + switch + " -show_entries packet=size -of compact=p=0:nk=1  '" + segment.file_path + "'"

    if os.path.isfile(segment.file_path + '.yaml'):
        with open(segment.file_path + '.yaml') as f_in:
            ydata = yaml.load(f_in, Loader=yaml.FullLoader)
            size = ydata['get_stream_size'][switch]
    else:
        stdout, _ = cmd_utils.run_command(
            cmd, name="get accumulated frame size for " + str(segment))
        size = sum([int(ll) for ll in stdout.split("\n") if ll != ""])

    return size
Ejemplo n.º 5
0
def get_audio_frame_info(segment):
    """
    Return a list of OrderedDicts with audio sample info, in presentation order

    Keys:
        - `segment`: basename of the segment file
        - `index`: index of the frame
        - `dts`: DTS of the sample
        - `size`: Size of the sample in bytes
        - `duration`: Duration of the sample in `s.msec`
    """
    cmd = "ffprobe -loglevel error -select_streams a -show_packets -show_entries packet=duration_time,size,dts_time -of json '" + segment.file_path + "'"
    stdout, _ = cmd_utils.run_command(cmd, name="get AFI for " + str(segment))
    info = json.loads(stdout)["packets"]
    ret = []
    index = 0
    for packet_info in info:
        ret.append(
            OrderedDict([('segment', segment.get_filename()), ('index', index),
                         ('dts', float(packet_info['dts_time'])),
                         ('size', int(packet_info['size'])),
                         ('duration', float(packet_info['duration_time']))]))
        index += 1
    return ret
Ejemplo n.º 6
0
def run(cli_args, test_config=None):

    if not test_config:
        test_config = cfg.TestConfig(cli_args.test_config, cli_args.filter_src,
                                     cli_args.filter_hrc, cli_args.filter_pvs)

    # get all pvs to be processed
    pvs_to_complete = []
    for pvs_id, pvs in test_config.pvses.items():
        if pvs.is_online() and cli_args.skip_online_services:
            continue
        pvs_to_complete.append(pvs)

    # aggregate/decode in parallel
    logger.info("will aggregate " + str(len(pvs_to_complete)) + " PVSes")
    cmd_list = []

    # concatenate segments if the test type is "long"
    if test_config.is_long():
        cmd_runner_concat = cmd_utils.ParallelRunner(cli_args.parallelism)

        pvs_commands = {}

        for pvs in pvs_to_complete:
            pvs_commands[pvs.pvs_id] = []
            # decode segments
            cmd_runner_segments = cmd_utils.ParallelRunner(
                cli_args.parallelism)

            segment_iter = 0
            for seg in pvs.segments:
                cmd = ffmpeg.create_avpvs_segment(
                    seg,
                    pvs,
                    overwrite=cli_args.force,
                    scale_avpvs_tosource=cli_args.avpvs_src_fps)
                cmd_name = "create AVPVS segment nr: " + str(
                    segment_iter) + " for " + str(pvs)
                cmd_runner_segments.add_cmd(cmd, name=str(cmd_name))
                segment_iter += 1

            pvs_commands[pvs.pvs_id].append(
                cmd_runner_segments.return_command_list())

            # concatenate segments
            cmd_concat = ffmpeg.create_avpvs_long_concat(
                pvs,
                overwrite=cli_args.force,
                scale_avpvs_tosource=cli_args.avpvs_src_fps)
            cmd_concat_name = "create AVPVS long for " + str(pvs)
            pvs_commands[pvs.pvs_id].append(cmd_concat)

            # add audio
            cmd_audio = ffmpeg.audio_mux(pvs, overwrite=cli_args.force)
            cmd_audio_name = "Muxing audio and video for " + str(pvs)
            pvs_commands[pvs.pvs_id].append(cmd_audio)

            # run or log all commands
            logger.debug(cmd_concat)
            logger.debug(cmd_audio)
            if cli_args.dry_run:
                cmd_runner_segments.log_commands()
            else:
                cmd_runner_segments.run_commands()
                cmd_utils.run_command(cmd_concat, name=str(cmd_concat_name))
                cmd_utils.run_command(cmd_audio, name=str(cmd_audio_name))

            # delete avpvs segments
            logger.info("Removing " + str(len(pvs.segments)) +
                        " avpvs segments")
            if not cli_args.dry_run:
                os.remove(pvs.get_avpvs_file_list())
                os.remove(pvs.get_tmp_wo_audio_path())
                for seg in pvs.segments:
                    os.remove(seg.get_tmp_path())

        # add stalling if needed
        pvs_with_buffering = [
            pvs for pvs in pvs_to_complete if pvs.has_buffering()
        ]
        cmd_runner_add_buffer = cmd_utils.ParallelRunner(cli_args.parallelism)
        if len(pvs_with_buffering):
            logger.info("will add stalling to " +
                        str(len(pvs_with_buffering)) + " PVSes")
            for pvs in pvs_with_buffering:
                input_file = pvs.get_avpvs_wo_buffer_file_path()
                output_file = pvs.get_avpvs_file_path()

                bufferstring = str(pvs.get_buff_events_media_time()).replace(
                    ' ', '')

                pix_fmt = pvs.get_pix_fmt_for_avpvs()

                if cli_args.force:
                    overwrite_spec = "-f"
                else:
                    overwrite_spec = ""

                cmd = 'bufferer -i {input_file} -o {output_file} -b {bufferstring} --force-framerate --black-frame' \
                      '-v ffv1 -a pcm_s16le -x {pix_fmt} -s {cli_args.spinner_path} {overwrite_spec}'.format(**locals())
                cmd_name = str(pvs) + ' buffering'

                cmd_runner_add_buffer.add_cmd(cmd, name=str(cmd_name))
                pvs_commands[pvs.pvs_id].append(cmd)

        if cli_args.dry_run:
            cmd_runner_add_buffer.log_commands()
            sys.exit(0)
        else:
            for pvs in pvs_to_complete:
                write_to_p03_logfile(pvs, pvs_commands[pvs.pvs_id])

        cmd_runner_add_buffer.run_commands()

        if cli_args.remove_intermediate:
            logger.info("removing " + str(len(pvs_with_buffering)) +
                        " intermediate video files")
            for pvs_name in pvs_with_buffering:
                os.remove(pvs.get_avpvs_wo_buffer_file_path())

    # Only run decoding if the test type is "short", only one segment assumed
    else:
        cmd_runner = cmd_utils.ParallelRunner(cli_args.parallelism)
        for pvs in pvs_to_complete:
            if cli_args.skip_online_services and pvs.is_online():
                logger.warn(
                    "Skipping PVS {} because it is an online PVS".format(pvs))
                continue
            current_command = ffmpeg.create_avpvs_short(
                pvs,
                overwrite=cli_args.force,
                scale_avpvs_tosource=cli_args.avpvs_src_fps)
            cmd_runner.add_cmd(current_command,
                               name="Create AVPVS short for " + str(pvs))

            if not cli_args.dry_run and current_command is not None:
                write_to_p03_logfile(pvs, [
                    current_command,
                ])

        if cli_args.dry_run:
            cmd_runner.log_commands()
            return test_config

        cmd_runner.run_commands()

    return test_config
Ejemplo n.º 7
0
def get_video_frame_info(segment, info_type="packet"):
    """
    Return a list of OrderedDicts with video frame info, in decoding or presentation order
    info_type: "packet" or "frame", if packet: decoding order, if frame: presentation order

    Return keys:
        - `segment`: basename of the segment file
        - `index`: index of the frame
        - `frame_type`: `I` or `Non-I` (for decoding order) or `I`, `P`, `B` (for presentation order)
        - `dts`: DTS of the frame (only for decoding order)
        - `pts`: PTS of the frame
        - `size`: Size of the packet in bytes (including SPS, PPS for first frame, and AUD units for subsequent frames)
        - `duration`: Duration of the frame in `s.msec`
    """
    if info_type == "packet":
        cmd = "ffprobe -loglevel error -select_streams v -show_packets -show_entries packet=pts_time,dts_time,duration_time,size,flags -of json '" + segment.file_path + "'"
    elif info_type == "frame":
        cmd = "ffprobe -loglevel error -select_streams v -show_frames -show_entries frame=pkt_pts_time,pkt_dts_time,pkt_duration_time,pkt_size,pict_type -of json '" + segment.file_path + "'"
    else:
        logger.error("wrong info type, can be 'packet' or 'frame'")
        sys.exit(1)

    stdout, _ = cmd_utils.run_command(cmd, name="get VFI for " + str(segment))
    info = json.loads(stdout)[info_type + "s"]

    # Assemble info into OrderedDict
    if info_type == "packet":
        ret = []
        index = 0

        default_duration = next(
            (x["duration_time"] for x in info if "duration_time" in x.keys()),
            "NaN")

        for packet_info in info:
            frame_type = "I" if packet_info['flags'] == "K_" else "Non-I"

            if 'dts_time' in packet_info.keys():
                dts = float(packet_info['dts_time'])
            else:
                dts = "NaN"

            if 'duration_time' in packet_info.keys():
                duration = float(packet_info['duration_time'])
            else:
                duration = default_duration

            ret.append(
                OrderedDict([('segment', segment.get_filename()),
                             ('index', index), ('frame_type', frame_type),
                             ('dts', dts), ('size', packet_info['size']),
                             ('duration', duration)]))
            index += 1

    elif info_type == "frame":
        ret = []
        index = 0
        for frame_info in info:
            if 'pts_time' in frame_info.keys():
                pts = float(frame_info['pts_time'])
            else:
                pts = "NaN"
            ret.append(
                OrderedDict([
                    ('segment', segment.get_filename()), ('index', index),
                    ('frame_type', frame_info['pict_type']), ('pts', pts),
                    ('size', int(frame_info['pkt_size'])),
                    ('duration', float(frame_info['pkt_duration_time']))
                ]))
            index += 1
    else:
        # cannot happen
        pass

    # fix for missing duration in VP9: estimate duration from DTS difference
    ret = fix_durations(ret)

    return ret
Ejemplo n.º 8
0
def get_segment_info(segment):
    """
    Get the info about the segment, as shown by ffprobe, for use in .qchanges file

    Returns an OrderedDict, with the keys:
    - `segment_filename`: Basename of the segment file
    - `file_size`: Size of the file in bytes
    - `video_duration`: Duration of the video in `s.msec`
    - `video_frame_rate`: Framerate in Hz
    - `video_bitrate`: Bitrate of the video stream in kBit/s
    - `video_target_bitrate`: Target bitrate of the video stream in kBit/s (may be empty/unknown)
    - `video_width`: Width in pixels
    - `video_height`: Height in pixels
    - `video_codec`: Video codec (`h264`, `hevc`, `vp9`)
    - `video_profile`: Video profile
    - `audio_duration`: Duration of the audio in `s.msec`
    - `audio_sample_rate`: Audio sample rate in Hz
    - `audio_codec`: Audio codec name (`aac`)
    - `audio_bitrate`: Bitrate of the video stream in kBit/s
    """
    input_file = segment.file_path

    if sys.platform == "darwin":
        cmd = "stat -f '%z' '" + input_file + "'"
    else:
        cmd = "stat -c '%s' '" + input_file + "'"
    stdout, _ = cmd_utils.run_command(cmd,
                                      name="get segment size for " +
                                      str(segment))
    segment_size = int(stdout.strip())

    cmd = "ffprobe -loglevel error -show_streams -of json '" + input_file + "'"
    stdout, _ = cmd_utils.run_command(cmd,
                                      name="get segment video info for " +
                                      str(segment))
    info = json.loads(stdout)
    has_video = False
    has_audio = False
    for stream_info in info["streams"]:
        if stream_info["codec_type"] == "video":
            video_info = stream_info
            has_video = True
        elif stream_info["codec_type"] == "audio":
            audio_info = stream_info
            has_audio = True

    if not has_video:
        logger.error("No video stream found in segment " + str(segment))
        sys.exit(1)

    if 'duration' in video_info.keys():
        video_duration = float(video_info['duration'])
    elif 'tags' in video_info.keys() and 'DURATION' in video_info['tags']:
        duration_str = video_info['tags']['DURATION']
        hms, msec = duration_str.split('.')
        total_dur = sum(
            int(x) * 60**i for i, x in enumerate(reversed(hms.split(":"))))
        video_duration = total_dur + float("0." + msec)
    else:
        info_type = 'packet'
        cmd = "ffprobe -loglevel error -select_streams v -show_packets -show_entries packet=pts_time,dts_time,duration_time,size,flags -of json '" + segment.file_path + "'"
        stdout, _ = cmd_utils.run_command(cmd,
                                          name="get VFI for " + str(segment))
        info = json.loads(stdout)[info_type + "s"]
        index = -1
        while True:
            packet_info = info[index]
            if 'dts_time' in packet_info.keys(
            ) and 'duration_time' in packet_info.keys():
                video_duration = float(
                    packet_info['dts_time']) + abs(index) * float(
                        packet_info['duration_time'])
                break
            index = index - 1
        logger.warning("Calculated duration of segment " + str(segment) +
                       " manually. Might not be perfectly accurate.")

    if not video_duration:
        logger.error(
            "Video duration of " + str(segment) +
            " was calculated as zero! Make sure that the input file is correct."
        )
        sys.exit(1)

    if 'bit_rate' in video_info.keys():
        video_bitrate = round(float(video_info['bit_rate']) / 1024.0, 2)
    else:
        # fall back to calculating from accumulated frame duration
        stream_size = get_stream_size(segment)
        video_bitrate = round((stream_size * 8 / 1024.0) / video_duration, 2)

    if hasattr(segment, "quality_level"):
        video_target_bitrate = segment.quality_level.video_bitrate
    else:
        video_target_bitrate = 0

    # override designation of video profile:
    if 'profile' in video_info.keys():
        video_profile = fix_video_profile_string(video_info['profile'])
    else:
        video_profile = ""

    ret = OrderedDict([('segment_filename', segment.filename),
                       ('file_size', segment_size),
                       ('video_duration', video_duration),
                       ('video_frame_rate',
                        float(Fraction(video_info['r_frame_rate']))),
                       ('video_bitrate', video_bitrate),
                       ('video_target_bitrate', video_target_bitrate),
                       ('video_width', video_info['width']),
                       ('video_height', video_info['height']),
                       ('video_codec', video_info['codec_name']),
                       ('video_profile', video_profile)])

    if has_audio:
        if 'duration' in audio_info.keys():
            audio_duration = float(audio_info['duration'])
        elif 'tags' in audio_info.keys() and 'DURATION' in audio_info['tags']:
            duration_str = audio_info['tags']['DURATION']
            hms, msec = duration_str.split('.')
            total_dur = sum(
                int(x) * 60**i for i, x in enumerate(reversed(hms.split(":"))))
            audio_duration = total_dur + float("0." + msec)
        elif 'nb_frames' in audio_info.keys():
            audio_duration = float(audio_info['nb_frames']) / float(
                audio_info['sample_rate'])
        else:
            logger.error("Could not extract audio duration from " +
                         str(segment))
            sys.exit(1)

        if 'bit_rate' in audio_info.keys():
            audio_bitrate = round(float(audio_info['bit_rate']) / 1024.0, 2)
        else:
            # fall back to calculating from accumulated frame duration
            stream_size = get_stream_size(segment, stream_type="audio")
            audio_bitrate = round((stream_size * 8 / 1024.0) / audio_duration,
                                  2)

        ret.update(
            OrderedDict([('audio_duration', audio_duration),
                         ('audio_sample_rate', audio_info['sample_rate']),
                         ('audio_codec', audio_info['codec_name']),
                         ('audio_bitrate', audio_bitrate)]))

    return ret
Ejemplo n.º 9
0
    def generate_full_segment(self, filename, codec, ten_bit=False, audio=False):

        if ten_bit:
            ffmpeg_version = "ffmpeg10"
        else:
            ffmpeg_version = "ffmpeg"

        root, ext = os.path.splitext(filename)
        full_video_path = os.path.join(self.video_segments_folder, filename)
        dload_path = os.path.join(self.video_segments_folder, root)
        video_outfile = os.path.join(dload_path, root + "_video_only" + ext)
        concat_cmd = ffmpeg_version + " -y -i " + video_outfile + " -strict -2 -c copy " + full_video_path

        video_init_element = None
        video_parts = []
        video_init_found = False

        directory = os.fsencode(dload_path)

        for video_part in os.listdir(directory):
            video_part_name = os.fsdecode(video_part)
            if video_part_name.endswith("init.hdr") and codec == "vp9" \
                    or video_part_name.endswith("init.mp4") and codec in ["h264", "h265", "hevc"]:
                if video_init_found:
                    logger.warning("Second init file found. Please clean your download folder " + dload_path)
                video_init_element = video_part_name
                video_init_found = True
                continue
            elif video_part_name.endswith(".chk") and codec == "vp9" \
                    or video_part_name.endswith(".m4s") and codec in ["h264", "h265", "hevc"]:
                raw_name = os.path.splitext(video_part_name)[0]
                part_number = int(raw_name.split("_")[-1])
                while len(video_parts) <= part_number:
                    video_parts.append("Dummy_entry")
                video_parts[part_number] = video_part_name
                continue
        if not video_init_found:
            logger.error("No init file found! Aborting")
            exit(-1)
        cmd = ffmpeg_version + """ -y -i \"concat:""" + os.path.join(dload_path, video_init_element)
        for video_part_name in video_parts:
            cmd = cmd + "|" + os.path.join(dload_path, video_part_name)

        cmd = cmd + """" -c copy -strict -2 """ + video_outfile
        cmd_utils.run_command(cmd)

        if audio:
            audio_init_element = None
            audio_parts = []
            audio_init_found = False

            dload_path = os.path.join(dload_path, "audio")
            logger.debug(dload_path)
            directory = os.fsencode(dload_path)

            audio_outfile = os.path.join(dload_path, root + "_audio_only.mp4")

            for audio_part in os.listdir(directory):
                audio_part_name = os.fsdecode(audio_part)
                logger.debug(audio_part_name)
                if audio_part_name.endswith("init.hdr") and codec == "vp9" \
                        or audio_part_name.endswith("init.mp4") and codec in ["h264", "h265", "hevc"]:
                    if audio_init_found:
                        logger.warning("Second init file found. Please clean your download folder " + dload_path)
                    audio_init_element = audio_part_name
                    audio_init_found = True
                    continue
                elif audio_part_name.endswith(".chk") and codec == "vp9" \
                        or audio_part_name.endswith(".m4s") and codec in ["h264", "h265", "hevc"]:
                    raw_name = os.path.splitext(audio_part_name)[0]
                    part_number = int(raw_name.split("_")[-1])
                while len(audio_parts) <= part_number:
                    audio_parts.append("Dummy_entry")
                    audio_parts[part_number] = audio_part_name
                    # audio_parts.append(audio_part_name)
                    continue

            if audio_init_found:
                # TODO: implement force overwriting
                cmd = ffmpeg_version + """ -y -i \"concat:""" + os.path.join(dload_path, audio_init_element)
                for audio_part_name in audio_parts:
                    cmd = cmd + "|" + os.path.join(dload_path, audio_part_name)

                cmd = cmd + """" -c copy -strict -2 """ + audio_outfile
                cmd_utils.run_command(cmd)

                concat_cmd = ffmpeg_version + " -y -i " + video_outfile + " -i " + audio_outfile + " -strict -2 -c copy " + full_video_path
            else:
                logger.warning("No audio file for " + root + " found. Will create a video without audio!")

        cmd_utils.run_command(concat_cmd)