Exemplo n.º 1
0
def set_target_quality(project):
    """
    Av1an setup for target_quality

    :param project: the Project
    """
    if project.vmaf_path:
        if not Path(project.vmaf_path).exists():
            print(f"No model with this path: {Path(project.vmaf_path).as_posix()}")
            terminate()

    if project.probes < 4:
        print('Target quality with less than 4 probes is experimental and not recommended')
        terminate()

    encoder = ENCODERS[project.encoder]

    if project.encoder not in ('x265', 'svt_av1') and project.target_quality_method == 'per_frame':
        print(f":: Per frame Target Quality is not supported for selected encoder\n:: Supported encoders: x265, svt_av1")
        exit()

    # setting range for q values
    if project.min_q is None:
        project.min_q, _ = encoder.default_q_range
        assert project.min_q > 1

    if project.max_q is None:
        _, project.max_q = encoder.default_q_range
Exemplo n.º 2
0
def create_video_queue_segment(args: Args, split_locations: List[int]) -> List[Chunk]:
    """
    Create a list of chunks using segmented files

    :param args: Args
    :param split_locations: a list of frames to split on
    :return: A list of chunks
    """

    # segment into separate files
    segment(args.input, args.temp, split_locations)

    # get the names of all the split files
    source_path = args.temp / 'split'
    queue_files = [x for x in source_path.iterdir() if x.suffix == '.mkv']
    queue_files.sort(key=lambda p: p.stem)

    if len(queue_files) == 0:
        er = 'Error: No files found in temp/split, probably splitting not working'
        print(er)
        log(er)
        terminate()

    chunk_queue = [create_chunk_from_segment(args, index, file) for index, file in enumerate(queue_files)]

    return chunk_queue
Exemplo n.º 3
0
def check_exes(project: Project):
    """
    Checking required executables

    :param project: the Project
    """

    if not find_executable('ffmpeg'):
        print('No ffmpeg')
        terminate()

    if project.chunk_method in ['vs_ffms2', 'vs_lsmash']:
        if not find_executable('vspipe'):
            print('vspipe executable not found')
            terminate()

        try:
            import vapoursynth
            plugins = vapoursynth.get_core().get_plugins()

            if project.chunk_method == 'vs_lsmash' and "systems.innocent.lsmas" not in plugins:
                print('lsmas is not installed')
                terminate()

            if project.chunk_method == 'vs_ffms2' and "com.vapoursynth.ffms2" not in plugins:
                print('ffms2 is not installed')
                terminate()
        except ModuleNotFoundError:
            print('Vapoursynth is not installed')
            terminate()
Exemplo n.º 4
0
def find_aom_keyframes(stat_file, key_freq_min):
    keyframes_list = []

    number_of_frames = round(os.stat(stat_file).st_size / 208) - 1
    dict_list = []
    try:
        with open(stat_file, 'rb') as file:
            frame_buf = file.read(208)
            while len(frame_buf) > 0:
                stats = struct.unpack('d' * 26, frame_buf)
                p = dict(zip(fields, stats))
                dict_list.append(p)
                frame_buf = file.read(208)
    except Exception as e:
        print('Get exception:', e)
        print('Recomended to switch to different method of scenedetection')
        terminate()

    # intentionally skipping 0th frame and last 16 frames
    frame_count_so_far = 1
    for i in range(1, number_of_frames - 16):
        is_keyframe = False

        # https://aomedia.googlesource.com/aom/+/ce97de2724d7ffdfdbe986a14d49366936187298/av1/encoder/pass2_strategy.c#2065
        if frame_count_so_far >= key_freq_min:
            is_keyframe = test_candidate_kf(dict_list, i, frame_count_so_far)
        if is_keyframe:
            keyframes_list.append(i)
            frame_count_so_far = 0
        frame_count_so_far += 1

    return keyframes_list
Exemplo n.º 5
0
    def match_line(self, line: str):
        """Extract number of encoded frames from line.

        :param line: one line of text output from the encoder
        :return: match object from re.search matching the number of encoded frames"""
        if 'error' in line.lower():
            print('\n\nERROR IN ENCODING PROCESS\n\n', line)
            terminate()
        return re.search(r"Encoding frame\s+(\d+)", line)
Exemplo n.º 6
0
    def match_line(self, line: str):
        """Extract number of encoded frames from line.

        :param line: one line of text output from the encoder
        :return: match object from re.search matching the number of encoded frames"""

        if 'fatal' in line.lower():
            print('\n\nERROR IN ENCODING PROCESS\n\n', line)
            terminate()
        if 'Pass 2/2' in line or 'Pass 1/1' in line:
            return re.search(r"frame.*?/([^ ]+?) ", line)
Exemplo n.º 7
0
def encoding_loop(project: Project, chunk_queue: List[Chunk]):
    """Creating process pool for encoders, creating progress bar."""
    if project.workers != 0:
        with concurrent.futures.ThreadPoolExecutor(max_workers=project.workers) as executor:
            future_cmd = {executor.submit(encode, cmd, project): cmd for cmd in chunk_queue}
            for future in concurrent.futures.as_completed(future_cmd):
                try:
                    future.result()
                except Exception as exc:
                    _, _, exc_tb = sys.exc_info()
                    print(f'Encoding error {exc}\nAt line {exc_tb.tb_lineno}')
                    terminate()
    project.counter.close()
Exemplo n.º 8
0
def check_exes(args: Args):
    """
    Checking required executables

    :param args: the Args
    """

    if not find_executable('ffmpeg'):
        print('No ffmpeg')
        terminate()

    if args.chunk_method == 'vs_ffms2' and (not find_executable('vspipe')):
        print('vspipe executable not found')
        terminate()
Exemplo n.º 9
0
def startup_check(project: Project):
    """
    Performing essential checks at startup_check
    Set constant values
    """
    if sys.version_info < (3, 6):
        print('Python 3.6+ required')
        sys.exit()
    if sys.platform == 'linux':
        def restore_term():
            os.system("stty sane")

        atexit.register(restore_term)

    if not project.chunk_method:
        select_best_chunking_method(project)

    # project.is_vs = is_vapoursynth(project.input)

    if project.is_vs:
        project.chunk_method = 'vs_ffms2'

    check_exes(project)

    set_target_quality(project)

    if project.reuse_first_pass and project.encoder != 'aom' and project.split_method != 'aom_keyframes':
        print('Reusing the first pass is only supported with \
              the aom encoder and aom_keyframes split method.')
        terminate()

    setup_encoder(project)

    # No check because vvc
    if project.encoder == 'vvc':
        project.no_check = True

    if project.encoder == 'svt_vp9' and project.passes == 2:
        print("Implicitly changing 2 pass svt-vp9 to 1 pass\n2 pass svt-vp9 isn't supported")
        project.passes = 1

    project.audio_params = shlex.split(project.audio_params)
    project.ffmpeg = shlex.split(project.ffmpeg)

    project.pix_format = ['-strict', '-1', '-pix_fmt', project.pix_format]
    project.ffmpeg_pipe = [*project.ffmpeg, *project.pix_format,
                        '-bufsize', '50000K', '-f', 'yuv4mpegpipe', '-']
Exemplo n.º 10
0
def startup_check(args: Args):
    """
    Performing essential checks at startup_check
    Set constant values
    """
    if sys.version_info < (3, 6):
        print('Python 3.6+ required')
        sys.exit()
    if sys.platform == 'linux':

        def restore_term():
            os.system("stty sane")

        atexit.register(restore_term)

    check_exes(args)

    set_vmaf(args)

    if args.reuse_first_pass and args.encoder != 'aom' and args.split_method != 'aom_keyframes':
        print('Reusing the first pass is only supported with \
              the aom encoder and aom_keyframes split method.')
        terminate()

    setup_encoder(args)

    # No check because vvc
    if args.encoder == 'vvc':
        args.no_check = True

    if args.encoder == 'svt_vp9' and args.passes == 2:
        print(
            "Implicitly changing 2 pass svt-vp9 to 1 pass\n2 pass svt-vp9 isn't supported"
        )
        args.passes = 1

    args.audio_params = shlex.split(args.audio_params)
    args.ffmpeg = shlex.split(args.ffmpeg)

    args.pix_format = ['-strict', '-1', '-pix_fmt', args.pix_format]
    args.ffmpeg_pipe = [
        *args.ffmpeg, *args.pix_format, '-bufsize', '50000K', '-f',
        'yuv4mpegpipe', '-'
    ]
Exemplo n.º 11
0
def setup_encoder(args: Args):
    """
    Settup encoder params and passes

    :param args: the Args
    """
    encoder = ENCODERS[args.encoder]

    # validate encoder settings
    settings_valid, error_msg = encoder.is_valid(args)
    if not settings_valid:
        print(error_msg)
        terminate()

    if args.passes is None:
        args.passes = encoder.default_passes

    args.video_params = encoder.default_args if args.video_params is None \
    else shlex.split(args.video_params)
Exemplo n.º 12
0
def concat_routine(project: Project):
    """
    Runs the concatenation routine with project

    :param project: the Project
    :return: None
    """
    try:
        if project.encoder == 'vvc':
            vvc_concat(project.temp, project.output_file.with_suffix('.h266'))
        elif project.mkvmerge:
            concatenate_mkvmerge(project.temp, project.output_file)
        else:
            concatenate_ffmpeg(project.temp, project.output_file, project.encoder)
    except Exception as e:
        _, _, exc_tb = sys.exc_info()
        print(f'Concatenation failed, error\nAt line: {exc_tb.tb_lineno}\nError:{str(e)}')
        log(f'Concatenation failed, aborting, error: {e}\n')
        terminate()
Exemplo n.º 13
0
def set_vmaf(args):
    """
    Av1an setup for VMAF

    :param args: the Args
    """
    if args.vmaf_path:
        if not Path(args.vmaf_path).exists():
            print(f'No such model: {Path(args.vmaf_path).as_posix()}')
            terminate()

    if args.vmaf_steps < 4:
        print('Target vmaf require more than 3 probes/steps')
        terminate()

    encoder = ENCODERS[args.encoder]

    if args.min_q is None:
        args.min_q, _ = encoder.default_q_range
    if args.max_q is None:
        _, args.max_q = encoder.default_q_range
Exemplo n.º 14
0
def setup_encoder(project: Project):
    """
    Setup encoder params and passes

    :param project: the Project
    """
    encoder = ENCODERS[project.encoder]

    # validate encoder settings
    settings_valid, error_msg = encoder.is_valid(project)
    if not settings_valid:
        print(error_msg)
        terminate()

    if project.passes is None:
        project.passes = encoder.default_passes

    project.video_params = encoder.default_args if project.video_params is None \
        else shlex.split(project.video_params)

    validate_inputs(project)
Exemplo n.º 15
0
def set_target_quality(project):
    """
    Av1an setup for VMAF

    :param project: the Project
    """
    if project.vmaf_path:
        if not Path(project.vmaf_path).exists():
            print(f"No model with this path: {Path(project.vmaf_path).as_posix()}")
            terminate()

    if project.probes < 4:
        print('Target quality with less than 4 probes is experimental and not recommended')
        terminate()

    encoder = ENCODERS[project.encoder]

    if project.min_q is None:
        project.min_q, _ = encoder.default_q_range
    if project.max_q is None:
        _, project.max_q = encoder.default_q_range
Exemplo n.º 16
0
def aom_keyframes(video_path: Path, stat_file, min_scene_len, ffmpeg_pipe,
                  video_params):
    """[Get frame numbers for splits from aomenc 1 pass stat file]
    """

    log(f'Started aom_keyframes scenedetection\nParams: {video_params}\n')

    # Get CV2 fast framecount
    video = cv2.VideoCapture(video_path.as_posix())
    total = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
    video.release()

    if total < 1:
        total = frame_probe(video_path)

    f, e = compose_aomsplit_first_pass_command(video_path, stat_file,
                                               ffmpeg_pipe, video_params)

    tqdm_bar = tqdm(total=total,
                    initial=0,
                    dynamic_ncols=True,
                    unit="fr",
                    leave=True,
                    smoothing=0.2)

    ffmpeg_pipe = subprocess.Popen(f, stdout=PIPE, stderr=STDOUT)
    pipe = subprocess.Popen(e,
                            stdin=ffmpeg_pipe.stdout,
                            stdout=PIPE,
                            stderr=STDOUT,
                            universal_newlines=True)

    encoder_history = deque(maxlen=20)
    frame = 0

    while True:
        line = pipe.stdout.readline()
        if len(line) == 0 and pipe.poll() is not None:
            break
        line = line.strip()

        if line:
            encoder_history.append(line)

        match = re.search(r"frame.*?/([^ ]+?) ", line)
        if match:
            new = int(match.group(1))
            if new > frame:
                tqdm_bar.update(new - frame)
            frame = new

    if pipe.returncode != 0 and pipe.returncode != -2:  # -2 is Ctrl+C for aom
        enc_hist = '\n'.join(encoder_history)
        er = f"\nAom first pass encountered an error: {pipe.returncode}\n{enc_hist}"
        log(er)
        print(er)
        if not stat_file.exists():
            terminate()
        else:
            # aom crashed, but created keyframes.log, so we will try to continue
            print(
                "WARNING: Aom first pass crashed, but created a first pass file. Keyframe splitting may not be accurate."
            )

    # aom kf-min-dist defaults to 0, but hardcoded to 3 in pass2_strategy.c test_candidate_kf. 0 matches default aom behavior
    # https://aomedia.googlesource.com/aom/+/8ac928be918de0d502b7b492708d57ad4d817676/av1/av1_cx_iface.c#2816
    # https://aomedia.googlesource.com/aom/+/ce97de2724d7ffdfdbe986a14d49366936187298/av1/encoder/pass2_strategy.c#1907
    min_scene_len = 0 if min_scene_len is None else min_scene_len

    keyframes = find_aom_keyframes(stat_file, min_scene_len)

    return keyframes