def fetch_details_ffprobe(self, _path: str) -> MediaInfo: ffprobe_path = str(PurePath(self.ffmpeg).parent.joinpath('ffprobe')) if not os.path.exists(ffprobe_path): return MediaInfo(None) args = [ ffprobe_path, '-v', '1', '-show_streams', '-print_format', 'json', '-i', _path ] with subprocess.Popen(args, stdout=subprocess.PIPE) as proc: output = proc.stdout.read().decode(encoding='utf8') info = json.loads(output) return MediaInfo.parse_details_json(_path, info)
def fetch_details(self, _path: str) -> MediaInfo: """Use HandBrakeCLI to get media information :param _path: Absolute path to media file :return: Instance of MediaInfo """ with subprocess.Popen([self.path, '--scan', '-i', _path], stderr=subprocess.PIPE) as proc: output = proc.stderr.read().decode(encoding='utf8') mi = MediaInfo.parse_handbrake_details(_path, output) if mi.valid: return mi return MediaInfo(None)
def test_stream_reassign_default(self): with open('tests/ffmpeg4.out', 'r') as ff: info = MediaInfo.parse_ffmpeg_details('/dev/null', ff.read()) setup = ConfigFile(self.get_setup()) p = setup.get_profile('excl_test_2') streams = info.ffmpeg_streams(p) self.assertEqual(len(streams), 8, 'expected 4 streams (8 elements)')
def test_stream_exclude(self): with open('tests/ffmpeg3.out', 'r') as ff: info = MediaInfo.parse_ffmpeg_details('/dev/null', ff.read()) setup = ConfigFile(self.get_setup()) p = setup.get_profile('excl_test_1') streams = info.ffmpeg_streams(p) self.assertEqual(len(streams), 12, 'expected 6 streams (12 elements)')
def test_stream_map_all(self): with open('tests/ffmpeg3.out', 'r') as ff: info = MediaInfo.parse_ffmpeg_details('/dev/null', ff.read()) setup = ConfigFile(self.get_setup()) p = setup.get_profile('qsv') streams = info.ffmpeg_streams(p) self.assertEqual(len(streams), 2, 'expected -map 0')
def test_skip_profile(self): with open('tests/ffmpeg.out', 'r') as ff: info = MediaInfo.parse_ffmpeg_details('/dev/null', ff.read()) info.filesize_mb = 499 config = ConfigFile(self.get_setup()) rule = config.match_rule(info) self.assertIsNotNone(rule, 'Expected rule match') self.assertTrue(rule.is_skip(), 'Expected a SKIP rule')
def match(self, media_info: MediaInfo) -> (str, str): if verbose: print(f' > evaluating "{self.name}"') if self.criteria is None: # no criteria section, match by default if verbose: print( f' >> rule {self.name} selected by default (no criteria)') return self.profile, self.name for pred, value in self.criteria.items(): inverted = False if pred not in valid_predicates: print(f'Invalid predicate {pred} in rule {self.name}') exit(1) if isinstance(value, str) and len(value) > 1 and value[0] == '!': inverted = True value = value[1:] if pred == 'vcodec' and not inverted and media_info.vcodec != value: if verbose: print( f' >> predicate vcodec ("{value}") did not match {media_info.vcodec}' ) break if pred == 'vcodec' and inverted and media_info.vcodec == value: if verbose: print( f' >> predicate vcodec ("{value}") matched {media_info.vcodec}' ) break if pred == 'path': try: match = re.search(value, media_info.path) if match is None: if verbose: print( f' >> predicate path ("{value}") did not match {media_info.path}' ) break except Exception as ex: print( f'invalid regex {media_info.path} in rule {self.name}') if verbose: print(str(ex)) exit(0) if pred in numeric_predicates: comp = media_info.eval_numeric(self.name, pred, value) if not comp and not inverted: # mismatch break if comp and inverted: # mismatch break else: # didn't bail out on any predicates, have a match return self.profile, self.name
def fetch_details(self, _path: str) -> MediaInfo: """Use ffmpeg to get media information :param _path: Absolute path to media file :return: Instance of MediaInfo """ with subprocess.Popen([self.ffmpeg, '-i', _path], stderr=subprocess.PIPE) as proc: output = proc.stderr.read().decode(encoding='utf8') mi = MediaInfo.parse_details(_path, output) if mi.valid: return mi # try falling back to ffprobe, if it exists try: return self.fetch_details_ffprobe(_path) except Exception as ex: print("Unable to fallback to ffprobe - " + str(ex)) return MediaInfo(None)
def test_mediainfo3(self): with open('tests/ffmpeg3.out', 'r') as ff: info = MediaInfo.parse_ffmpeg_details('/dev/null', ff.read()) self.assertIsNotNone(info) self.assertEqual(info.vcodec, 'hevc') self.assertEqual(info.res_width, 3840) self.assertEqual(info.fps, 23) self.assertEqual(info.runtime, (2 * 3600) + (5 * 60) + 53) self.assertEqual(info.path, '/dev/null') self.assertEqual(info.colorspace, 'yuv420p10le')
def test_mediainfo2(self): with open('tests/ffmpeg2.out', 'r') as ff: info = MediaInfo.parse_ffmpeg_details('/dev/null', ff.read()) self.assertIsNotNone(info) self.assertEqual(info.vcodec, 'h264') self.assertEqual(info.res_width, 1920) self.assertEqual(info.fps, 24) self.assertEqual(info.runtime, (52 * 60) + 49) self.assertEqual(info.path, '/dev/null') self.assertEqual(info.colorspace, 'yuv420p')
def test_mediainfo(self): with open('tests/ffmpeg.out', 'r') as ff: info = MediaInfo.parse_details('/dev/null', ff.read()) self.assertIsNotNone(info) self.assertEqual(info.vcodec, 'h264') self.assertEqual(info.res_width, 1280) self.assertEqual(info.fps, 23) self.assertEqual(info.runtime, (2 * 3600) + (9 * 60) + 38) self.assertEqual(info.path, '/dev/null') self.assertEqual(info.colorspace, 'yuv420p')
def fetch_details(self, _path: str) -> MediaInfo: """Use ffmpeg to get media information :param _path: Absolute path to media file :return: Instance of MediaInfo """ with subprocess.Popen([self.ffmpeg, '-i', _path], stderr=subprocess.PIPE) as proc: output = proc.stderr.read().decode(encoding='utf8') return MediaInfo.parse_details(_path, output)
def make_media(path, vcodec, res_width, res_height, runtime, source_size, fps, colorspace, audio, subtitle) -> MediaInfo: info = { 'path': path, 'vcodec': vcodec, 'stream': 0, 'res_width': res_width, 'res_height': res_height, 'runtime': runtime, 'filesize_mb': source_size, 'fps': fps, 'colorspace': colorspace, 'audio': audio, 'subtitle': subtitle } return MediaInfo(info)