Exemplo n.º 1
0
def process_bd(bd, is_bdmv, args):
    '''
    Processes the given bd.
    :param bd: the (pybluread) Bluray
    :param is_bdmv: true if the search target was an index.bdmv
    :param args: the cli args.
    '''
    from madmeasurer.loggers import main_logger, output_logger, csv_logger
    with mount_if_necessary(bd.Path, args) as bd_folder_path:
        if args.measure is True or args.copy is True:
            process_measurements(bd, bd_folder_path, args)
        elif args.analyse_main_algos is True:
            m1 = bd.GetTitle(get_main_title_by_duration(bd)).Playlist
            m2 = bd.GetTitle(get_main_title_by_mpc_be(bd, bd_folder_path)).Playlist
            m3 = bd.GetTitle(bd.MainTitleNumber).Playlist
            m4 = bd.GetTitle(get_main_title_by_jriver(bd, bd_folder_path)).Playlist
            m5 = bd.GetTitle(get_main_title_by_jriver(bd, bd_folder_path, resolution='minutes')).Playlist
            csv_logger.error(f"\"{bd.Path}\",{m1},{m2},{m3},{m4},{m5},{len({m1, m2, m3, m4, m5})}")
        else:
            main_titles = get_main_titles(bd, bd_folder_path, args).values()
            if is_any_title_uhd(bd.Path, main_titles) or args.include_hd is True:
                if args.silent:
                    for t in main_titles:
                        output_logger.error(t.Playlist)
                else:
                    for t in main_titles:
                        if is_bdmv is True:
                            output_logger.error(f"{os.path.abspath(os.path.join(bd.Path, 'BDMV', 'PLAYLIST', t.Playlist))}")
                        else:
                            output_logger.error(f"{os.path.abspath(bd.Path)},{t.Playlist}")
            else:
                main_logger.info(f"Ignoring non UHD BD - {bd.Path}")
        if args.describe_bd is True:
            describe_bd(bd, bd_folder_path, force=args.force, verbose=args.verbose is not None and args.verbose > 2)
Exemplo n.º 2
0
def __should_trigger_measurement(args, measurement_file):
    from madmeasurer.loggers import main_logger
    if args.force is True:
        main_logger.warning(f"Remeasuring : {measurement_file} exists, force is true")
        return True
    else:
        main_logger.info(f"Ignoring : {measurement_file} exists, force is false")
        return False
Exemplo n.º 3
0
def process_mkv(target, args):
    '''
    Examines the mkv with enzyme and checks if it is a UHD file.
    :param args: the cli args.
    :param target: the full path to the matched file.
    '''
    if args.include_hd is True or __is_uhd_mkv(target) is True:
        do_measure_if_necessary(target, args)
    else:
        from madmeasurer.loggers import main_logger
        main_logger.info(f"Ignoring {target}, is not UHD and include-hd is false")
Exemplo n.º 4
0
def copy_measurements(bd_folder_path, main_playlist, args):
    '''
    Copies an existing index.bdmv measurements file to the correct location for the main title.
    :param bd_folder_path: the bd folder path.
    :param main_playlist: the main playlist file name.
    :param args: the cli args.
    '''
    from madmeasurer.loggers import main_logger
    src_file = os.path.join(bd_folder_path, 'BDMV', 'index.bdmv.measurements')
    dest_file = os.path.join(bd_folder_path, 'BDMV', 'PLAYLIST', f"{main_playlist}.measurements")
    copy_it = False
    if os.path.exists(src_file):
        if os.path.exists(dest_file):
            if args.force is True:
                main_logger.info(f"Overwriting : {src_file} with {dest_file} as force=True")
                copy_it = True
            else:
                main_logger.info(f"Ignoring : {dest_file} exists and force=False")
        else:
            copy_it = True
            main_logger.info(f"Creating : {src_file} -> {dest_file}")
    else:
        main_logger.info(f"Ignoring : {src_file}, does not exist")
    if copy_it:
        if args.dry_run is True:
            main_logger.warning(f"DRY RUN! Copying {src_file} to {dest_file}")
        else:
            main_logger.warning(f"Copying {src_file} to {dest_file}")
            shutil.copy2(src_file, dest_file)
            main_logger.warning(f"Copied {src_file} to {dest_file}")
Exemplo n.º 5
0
def run_mad_measure_hdr(measure_target, args):
    '''
    triggers madMeasureHDR and bridges the stdout back to this process stdout live
    :param args: the cli args.
    :param measure_target: file to measure.
    '''
    from madmeasurer.loggers import main_logger, output_logger
    exe = "" if args.mad_measure_path is None else f"{args.mad_measure_path}{os.path.sep}"
    command = [os.path.abspath(f"{exe}madMeasureHDR.exe"), os.path.abspath(measure_target)]
    if args.dry_run is True:
        main_logger.error(f"DRY RUN! Triggering : {command}")
    else:
        if not os.path.isfile(command[0]):
            main_logger.error(f"FAILED! madMeasureHDR.exe not found at {command[0]}")
        else:
            main_logger.info(f"Triggering : {command}")
            txt_output = os.path.abspath(f"{measure_target}-madvr.txt")
            with open(txt_output, 'w') as details:
                process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=4)
                line_num = 0
                output = None
                tmp_output = None
                while True:
                    if line_num == 0:
                        output = process.stdout.readline().decode('utf-8')
                        line_num = 1
                    elif line_num == 1:
                        tmp = process.stdout.read(1)
                        if tmp == b'\x08':
                            output = tmp_output
                            tmp_output = ''
                        elif tmp == b'':
                            output = tmp_output
                            tmp_output = ''
                        else:
                            tmp_output = tmp_output + tmp.decode('utf-8')
                    if output is not None:
                        if output == '' and process.poll() is not None:
                            break
                        if output:
                            txt = output.strip()
                            output_logger.error(txt)
                            details.write(txt + '\n')
                            details.flush()
                    output = None
                rc = process.poll()
                if rc == 0:
                    main_logger.error(f"Completed OK {command}")
                else:
                    main_logger.error(f"FAILED {command}")
Exemplo n.º 6
0
def get_main_title_by_mpc_be(bd, bd_folder_path):
    '''
    Locates the main using the MPC-BE algorithm.
    :param bd: the pyblueread Bluray.
    :param bd_folder_path: the path to the bd folder.
    :return: the main title number.
    '''
    main_title_playlist = ''
    main_title_number = -1
    max_duration = 0
    max_duration_fancy = ''
    max_video_res = 0
    max_playlist_file_size = 0
    for title_number in range(bd.NumberOfTitles):
        title = bd.GetTitle(title_number)
        video_res = get_max_video_resolution(title)
        playlist_file_size = get_playlist_file_size(bd_folder_path, title)
        if (
                (title.Length > max_duration and video_res >= max_video_res)
                or (title.Length == max_duration and playlist_file_size > max_playlist_file_size)
                or ((max_duration > title.Length > max_duration / 2) and video_res > max_video_res)
        ):
            if main_title_number != -1:
                main_logger.info(f"Updating main title from {main_title_playlist} to {title.Playlist}")
                main_logger.info(f"   duration:  {max_duration_fancy} -> {title.LengthFancy}")
                main_logger.info(f"   video_res: {max_video_res} -> {video_res}")
                main_logger.info(f"   file_size: {max_playlist_file_size} -> {playlist_file_size}")
            main_title_number = title_number
            main_title_playlist = title.Playlist
            max_duration = title.Length
            max_duration_fancy = title.LengthFancy
            max_video_res = video_res
            max_playlist_file_size = playlist_file_size

    return main_title_number
Exemplo n.º 7
0
def dismount_iso_on_windows(iso):
    '''
    Dismounts the ISO.
    :param iso: the iso.
    '''
    iso_to_dismount = os.path.abspath(iso)
    command = f"PowerShell Dismount-DiskImage {iso_to_dismount}"
    main_logger.debug(f"Triggering : {command}")
    result = subprocess.run(command, capture_output=True)
    if result is not None and result.returncode == 0:
        main_logger.info(f"Dismounted {iso_to_dismount}")
    else:
        main_logger.error(
            f"Unable to dismount {iso_to_dismount} , stdout: {result.stdout.decode('utf-8')}, stderr: {result.stderr.decode('utf-8')}"
        )
Exemplo n.º 8
0
def get_main_title_by_duration(b):
    '''
    Locates the main title using a LAVSplitter algorithm.
    :param b: the pybluread Bluray.
    :return: the main title number.
    '''
    longest_duration = 0
    main_title_number = -1
    for title_number in range(b.NumberOfTitles):
        title = b.GetTitle(title_number)
        if title.Length > longest_duration:
            if main_title_number != -1:
                main_logger.info(
                    f"Updating main title from {main_title_number} to {title_number}, duration was {longest_duration} is {title.Length}")
            main_title_number = title_number
            longest_duration = title.Length
    return main_title_number
Exemplo n.º 9
0
def do_measure_if_necessary(target_file, args):
    '''
    Triggers madMeasureHDR if the title is a UHD and the measurements file for the playlist does not exist.
    :param target_file: the file to measure
    :param args: the cli args.
    '''
    from madmeasurer.loggers import main_logger
    measurement_file = f"{target_file}.measurements"
    incomplete_measurements_file = f"{measurement_file}.incomplete"
    if os.path.exists(measurement_file):
        trigger_it = __should_trigger_measurement(args, measurement_file)
    elif os.path.exists(incomplete_measurements_file):
        trigger_it = __should_trigger_measurement(args, incomplete_measurements_file)
    else:
        main_logger.info(f"Measuring : {measurement_file} does not exist")
        trigger_it = True
    if trigger_it:
        run_mad_measure_hdr(target_file, args)
Exemplo n.º 10
0
def mount_iso_on_windows(iso):
    '''
    Mounts ISO and returns the mounted drive path.
    :param iso: the iso.
    :return: the mounted path.
    '''
    iso_to_mount = os.path.abspath(iso)
    command = f"PowerShell ((Mount-DiskImage {iso_to_mount} -PassThru) | Get-Volume).DriveLetter"
    main_logger.debug(f"Triggering : {command}")
    result = subprocess.run(command, capture_output=True)
    if result is not None and result.returncode == 0:
        target = f"{result.stdout.decode('utf-8').rstrip()}:{os.path.sep}"
        main_logger.info(f"Mounted {iso_to_mount} on {target}")
    else:
        main_logger.error(
            f"Unable to mount {iso_to_mount} , stdout: {result.stdout.decode('utf-8')}, stderr: {result.stderr.decode('utf-8')}"
        )
        target = None
    return target
Exemplo n.º 11
0
def process_measurements(bd, bd_folder_path, args):
    '''
    Creates measurement files by measuring or copying as necessary for the requested titles.
    :param bd: the libbluray Bluray wrapper.
    :param bd_folder_path: the physical path to the bd folder.
    :param args: the cli args
    '''
    from madmeasurer.loggers import main_logger
    main_titles = get_main_titles(bd, bd_folder_path, args)
    if args.measure is True:
        for title_number in range(bd.NumberOfTitles):
            measure_it = False
            title = bd.GetTitle(title_number)
            if is_any_title_uhd(bd.Path, main_titles.values()):
                if title_number in main_titles.keys():
                    measure_it = True
                    main_logger.debug(f"Measurement candidate {bd.Path} - {title.Playlist} : main title")
                elif args.measure_all_playlists is True:
                    from bluread.objects import TicksToTuple
                    title_duration = TicksToTuple(title.Length)
                    title_duration_mins = (title_duration[0] * 60) + title_duration[1]
                    if title_duration_mins >= args.min_duration:
                        if args.max_duration is None or title_duration_mins <= args.max_duration:
                            main_logger.info(f"Measurement candidate {bd.Path} - {title.Playlist} : length is {title.LengthFancy}")
                            measure_it = True
                if measure_it is True:
                    playlist_file = os.path.join(bd_folder_path, 'BDMV', 'PLAYLIST', title.Playlist)
                    do_measure_if_necessary(playlist_file, args)
                else:
                    main_logger.debug(f"No measurement required for {bd.Path} - {title.Playlist}")
            else:
                main_logger.debug(f"Ignoring non uhd title {bd.Path} - {title.Playlist}")

    if args.copy is True:
        for t in main_titles.values():
            copy_measurements(bd.Path, t.Playlist, args)
Exemplo n.º 12
0
def search_path(path, args, match_type, depth):
    '''
    Searches for BDs to handle in the given path.
    :param path: the search path.
    :param args: the cli args.
    :param match_type: the type of file to find.
    :param the search depth:
    '''
    from madmeasurer.loggers import main_logger
    depth = '/**' if depth == -1 else ''.join(['/*'] * depth)
    if os.path.exists(path) and os.path.isfile(path):
        glob_str = path
        is_index_bdmv = Path(path).name == 'index.bdmv'
    else:
        if path[-1] == '/' or path[-1] == '\\':
            path = path[0:-1]
        glob_str = f"{path}{depth}/{match_type}"
        main_logger.info(f"Searching {glob_str}")
        is_index_bdmv = match_type == 'BDMV/index.bdmv'

    match_type_desc = 'BD' if match_type == 'BDMV/index.bdmv' else match_type[2:] + ' file'
    bds_processed = 0
    for match in glob(glob_str, recursive=True):
        if bds_processed > 0 and bds_processed % 10 == 0:
            main_logger.warning(f"Processed {bds_processed} {match_type_desc}s")
        target = match if not is_index_bdmv else str(Path(match).parent.parent)
        if is_index_bdmv or match_type == '*.iso':
            open_and_process_bd(args, os.path.abspath(target), is_index_bdmv)
            bds_processed = bds_processed + 1
        elif match_type[2:] == 'mkv':
            process_mkv(os.path.abspath(target), args)
        else:
            main_logger.info(f"Target found for {match_type}, measuring {target}")
            do_measure_if_necessary(os.path.abspath(target), args)

    main_logger.warning(f"Completed search of {glob_str}, processed {bds_processed} {match_type_desc}{'' if bds_processed == 1 else 's'}")
Exemplo n.º 13
0
def open_and_process_bd(args, target, is_bdmv):
    '''
    Opens the BD with libbluray and processes it.
    :param args: the cli args.
    :param match: the full path to the matched file.
    :param target: the path to the root of the BD.
    :param is_bdmv: true if the search target was an index.bdmv
    '''
    from madmeasurer.loggers import main_logger
    import bluread
    main_logger.info(f"Opening {target}")
    with bluread.Bluray(target) as bd:
        try:
            bd.Open(flags=0x03, min_duration=args.min_duration * 60)
            process_bd(bd, is_bdmv, args)
        except Exception as e:
            if 'Failed to get titles' in str(e):
                main_logger.info(f"{target} has no titles longer than {args.min_duration}, ignoring")
            else:
                main_logger.exception(f"Unable to read {target}, ignoring")
    main_logger.info(f"Closing {target}")
Exemplo n.º 14
0
def main():
    arg_parser = argparse.ArgumentParser(description='madmeasurer for BDMV')
    arg_parser.add_argument('paths',
                            default=[os.getcwd()],
                            nargs='+',
                            help='Search paths')

    group = arg_parser.add_argument_group('Search')
    group.add_argument(
        '-d',
        '--exact-depth',
        type=int,
        help=
        'Sets the search depth to the specific folder depth only, e.g. if -d 2 then search for <path>/*/*/BDMV/index.bdmv . If neither --exact-depth nor --max-depth is set then search for /** unless the path is to a specific file'
    )
    group.add_argument(
        '--max-depth',
        type=int,
        help=
        'Sets the maximum folder depth to search, e.g. if --max-depth 2 then search for <path>/BDMV/index.bdmv and <path>/*/BDMV/index.bdmv and <path>/*/*/BDMV/index.bdmv. If neither --exact-depth nor --max-depth is set then search for /** unless the path is to a specific file'
    )
    group.add_argument('-i',
                       '--iso',
                       action='store_true',
                       default=False,
                       help='Search for ISO files instead of index.bdmv')
    group.add_argument('-e',
                       '--extension',
                       action='append',
                       help='Search for files with the specified extension(s)')
    group.add_argument(
        '--min-duration',
        type=int,
        default=30,
        help=
        'Minimum playlist duration in minutes to be considered a main title or measurement candidate'
    )
    group.add_argument(
        '--main-by-libbluray',
        action='store_true',
        default=True,
        help=
        'Finds main titles via the libbluray algorithm, this is the default algorithm'
    )
    group.add_argument('--no-main-by-libbluray',
                       action='store_const',
                       const='False',
                       dest='main_by_libbluray',
                       help='Disables use of the libbluray algorithm')
    group.add_argument(
        '--main-by-duration',
        action='store_true',
        default=False,
        help=
        'Finds the main title by comparing playlist duration only (as per LAVSplitter BDDemuxer)'
    )
    group.add_argument(
        '--main-by-mpc-be',
        action='store_true',
        default=False,
        help='Finds the main title via the mpc-be HdmvClipInfo algorithm')
    group.add_argument('--main-by-jriver',
                       action='store_true',
                       default=False,
                       help='Finds the main title via the JRiver algorithm')
    group.add_argument(
        '--main-by-jriver-minute-resolution',
        action='store_true',
        default=False,
        help=
        'Finds the main title via the JRiver algorithm using minute resolution when comparing durations'
    )
    group.add_argument('--include-hd',
                       action='store_true',
                       default=False,
                       help='Extend search to cover non UHD BDs')

    group = arg_parser.add_argument_group('Measure')
    group.add_argument(
        '-f',
        '--force',
        action='store_true',
        default=False,
        help=
        'if a playlist measurement file already exists, overwrite it from index.bdmv anyway'
    )
    group.add_argument(
        '-c',
        '--copy',
        action='store_true',
        default=False,
        help=
        'Copies index.bdmv.measurements to the specified main title location')
    group.add_argument(
        '-m',
        '--measure',
        action='store_true',
        default=False,
        help=
        'Calls madMeasureHDR.exe if no measurement file exists and the main title is a UHD'
    )
    group.add_argument(
        '--mad-measure-path',
        action=EnvDefault,
        required=False,
        envvar='MAD_MEASURE_HDR_PATH',
        help=
        'Path to madMeasureHDR.exe (can set via MAD_MEASURE_HDR_PATH env var)')
    group.add_argument(
        '--measure-all-playlists',
        action='store_true',
        default=False,
        help=
        'Use with -m to also measure playlists longer than min-duration (and shorter than max-duration if supplied)'
    )
    group.add_argument(
        '--max-duration',
        type=int,
        help=
        'Maximum playlist duration in minutes for measurements candidates, applies to --measure-all-playlists only'
    )

    group = arg_parser.add_argument_group('Output')
    group.add_argument('-v',
                       '--verbose',
                       action='count',
                       help='''
        Output additional logging
        Can be added multiple times
        Use -vvv to see additional debug logging from libbluray
        ''')
    group.add_argument(
        '-s',
        '--silent',
        action='store_true',
        default=False,
        help=
        'Print the main title name only (NB: only make sense when searching for one title)'
    )
    group.add_argument(
        '--analyse-main-algos',
        action='store_true',
        default=False,
        help=
        'Produces a report showing which titles are determined as the main title'
    )
    group.add_argument('--dry-run',
                       action='store_true',
                       default=False,
                       help='Execute without changing any files')
    group.add_argument(
        '--bd-debug-mask',
        help=
        'Specifies a debug mask to be passed as BD_DEBUG_MASK for libbluray')
    group.add_argument(
        '--describe-bd',
        action='store_true',
        default=False,
        help=
        'Outputs a description of the disc in YAML format to the BD folder directory'
    )

    parsed_args = arg_parser.parse_args(sys.argv[1:])
    os.environ['BD_DEBUG_MASK'] = '0x0'
    if parsed_args.verbose is None or parsed_args.verbose == 0:
        main_logger.setLevel(logging.ERROR)
    elif parsed_args.verbose == 1:
        main_logger.setLevel(logging.WARNING)
    elif parsed_args.verbose == 2:
        main_logger.setLevel(logging.INFO)
    else:
        main_logger.setLevel(logging.DEBUG)
        os.environ['BD_DEBUG_MASK'] = '0x00140'

    if parsed_args.bd_debug_mask is not None:
        main_logger.info(
            f"Overriding BD_DEBUG_MASK - {parsed_args.bd_debug_mask}")
        os.environ['BD_DEBUG_MASK'] = parsed_args.bd_debug_mask

    file_types = []
    if parsed_args.iso is True:
        file_types.append('*.iso')
    else:
        if parsed_args.extension is not None and len(
                parsed_args.extension) > 0:
            for e in parsed_args.extension:
                file_types.append(f"*.{e}")
        else:
            file_types.append('BDMV/index.bdmv')

    if parsed_args.analyse_main_algos:
        try:
            os.mkdir('report')
        except FileExistsError:
            pass
        csv_handler = logging.FileHandler('report/main_report.csv', mode='w+')
        csv_formatter = logging.Formatter('%(message)s')
        csv_handler.setFormatter(csv_formatter)
        csv_logger.addHandler(csv_handler)
        csv_logger.addHandler(output_handler)
        csv_logger.error('BD,Duration,MPC-BE,libbluray,jriver,Count')

    if parsed_args.measure_all_playlists is True \
            and parsed_args.max_duration is not None \
            and parsed_args.max_duration <= parsed_args.min_duration:
        raise ValueError(
            f"--max-duration {parsed_args.max_duration} is less than --min-duration {parsed_args.min_duration}"
        )

    for p in parsed_args.paths:
        if p[-14:] == 'index.bluray;1':
            new_path = p[0:-19]
            main_logger.info(
                f"J River library entry detected, swapping {p} for {new_path}")
            p = new_path
        for file_type in file_types:
            if os.path.exists(p) and os.path.isfile(p):
                search_path(p, parsed_args, file_type, 0)
            else:
                if parsed_args.exact_depth is not None or parsed_args.max_depth is not None:
                    if parsed_args.exact_depth is not None:
                        min_depth = parsed_args.exact_depth
                        max_depth = min_depth
                    else:
                        min_depth = 0
                        max_depth = parsed_args.max_depth
                    for depth in range(min_depth, max_depth + 1):
                        search_path(p, parsed_args, file_type, depth)
                else:
                    search_path(p, parsed_args, file_type, -1)