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')}" )
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
def __read_playlists_from_disc_inf(bd, bd_folder_path): candidate_titles = {} if os.path.exists(os.path.join(bd_folder_path, 'disc.inf')): obfuscated_playlists = None with open(os.path.join(bd_folder_path, 'disc.inf'), mode='r') as f: for line in f: if line.startswith('playlists='): obfuscated_playlists = [f"{int(x.strip()):05}.mpls" for x in line[10:].split(',')] break if obfuscated_playlists is not None: main_logger.debug(f"disc.inf found with obfuscated playlists {obfuscated_playlists}") for x in range(bd.NumberOfTitles): t = bd.GetTitle(x) if t.Playlist in obfuscated_playlists: candidate_titles[x] = t else: main_logger.debug('No playlists found in disc.inf') else: main_logger.debug('No disc.inf found') return candidate_titles
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)
def get_main_title_by_jriver(bd, bd_folder_path, resolution='seconds'): ''' Locates the main title using JRiver's algorithm which compares entries one by one by duration, audio stream count and then playlist name albeit allowing a slightly (within 10%) shorter track with more audio streams to still win. :param bd: the pybluread BD. :param bd_folder_path: the folder path. :param resolution: the resolution to use when comparison durations. :return: the main title. ''' candidate_titles = __read_playlists_from_disc_inf(bd, bd_folder_path) if len(candidate_titles) == 0: candidate_titles = {x: bd.GetTitle(x) for x in range(bd.NumberOfTitles)} max_audio_titles = 0 main_title = None main_title_num = 0 for title_num, title in candidate_titles.items(): new_main = False reason = '' if main_title is None: new_main = True else: audio_titles = __get_max_audio(title) cmp = 0 if title.Length >= (main_title.Length*0.9): cmp = audio_titles - max_audio_titles if cmp == 0: this_len = TicksToTuple(title.Length) main_len = TicksToTuple(main_title.Length) if resolution == 'minutes': cmp = ((this_len[0] * 60) + this_len[1]) - ((main_len[0] * 60) + main_len[1]) elif resolution == 'seconds': cmp = ((this_len[0] * 60 * 60) + (this_len[1] * 60) + this_len[2]) \ - ((main_len[0] * 60 * 60) + (main_len[1] * 60) + main_len[2]) else: cmp = title.Length - main_title.Length if cmp == 0: cmp = audio_titles - max_audio_titles if cmp == 0: if title.Playlist < main_title.Playlist: cmp = 1 reason = 'playlist name order' else: reason = 'audio stream count' else: reason = 'duration' elif cmp > 0: reason = 'audio stream count, duration within 10%' if cmp > 0: new_main = True if new_main is True: if main_title is not None: main_logger.debug(f"New main title found {title.Playlist} vs {main_title.Playlist} : {reason}") else: main_logger.debug(f"Initialising main title search with {title.Playlist}") main_title = title main_title_num = title_num max_audio_titles = __get_max_audio(title) else: main_logger.debug(f"Main title remains {main_title.Playlist}, discarding {title.Playlist}") if main_title is None: main_logger.error(f"No main title found in {bd.Path}") return main_title_num
def describe_bd(bd, bd_folder_path, force=False, verbose=False): ''' Outputs a yaml file into the bd folder describing the BD. :param bd: the libbluray BD. :param bd_folder_path: the BD folder path. :param force: overwrite the file if it exists. :param verbose: if true, dump the output to the screen ''' output_file = os.path.join(bd_folder_path, 'disc.yaml') if not os.path.exists(output_file) or force is True: details = {'name': bd.Path, 'title_count': bd.NumberOfTitles, 'main_titles': main_title_by_algo(bd, bd_folder_path)} titles = [] for title_number in range(bd.NumberOfTitles): t = bd.GetTitle(title_number) title = {'idx': title_number, 'playlist': t.Playlist, 'duration_raw': t.Length, 'duration': t.LengthFancy, 'angle_count': t.NumberOfAngles, 'chapter_count': t.NumberOfChapters} chapters = [] for chapter_number in range(1, t.NumberOfChapters + 1): c = t.GetChapter(chapter_number) chapter = {'idx': chapter_number, 'start_raw': c.Start, 'start': c.StartFancy, 'end_raw': c.End, 'end': c.EndFancy, 'duration_raw': c.Length, 'duration': c.LengthFancy} chapters.append(chapter) title['chapters'] = chapters title['clip_count'] = t.NumberOfClips clips = [] for clip_number in range(t.NumberOfClips): c = t.GetClip(clip_number) clip = {'idx': clip_number, 'video_primary_count': c.NumberOfVideosPrimary, 'video_secondary_count': c.NumberOfVideosSecondary} videos = [] for video_number in range(c.NumberOfVideosPrimary): v = c.GetVideo(video_number) video = {'idx': video_number, 'language': v.Language, 'coding_type': v.CodingType, 'format': v.Format, 'rate': v.Rate, 'aspect': v.Aspect} videos.append(video) clip['video_primary'] = videos clip['audio_primary_count'] = c.NumberOfAudiosPrimary clip['audio_secondary_count'] = c.NumberOfAudiosSecondary audios = [] for audio_number in range(c.NumberOfAudiosPrimary): a = c.GetAudio(audio_number) audio = {'idx': audio_number, 'language': a.Language, 'coding_type': a.CodingType, 'format': a.Format, 'rate': a.Rate} audios.append(audio) clip['audio_primary'] = audios clip['subtitle_count'] = c.NumberOfSubtitles subtitles = [] for subtitle_number in range(c.NumberOfSubtitles): s = c.GetSubtitle(subtitle_number) subtitle = {'idx': subtitle_number, 'language': s.Language} subtitles.append(subtitle) clip['subtitles'] = subtitles clips.append(clip) title['clips'] = clips titles.append(title) details['titles'] = titles with open(output_file, 'w+') as f: yaml.dump(details, f) if verbose is True: main_logger.debug(yaml.dump(details))