def parse(app: FastFlixApp, **_): source = app.fastflix.current_video.source if source.name.lower().endswith("txt"): source = get_first_concat_item(source) app.fastflix.current_video.concat = True data = probe(app, source) if "streams" not in data: raise FlixError(f"Not a video file, FFprobe output: {data}") streams = Box({"video": [], "audio": [], "subtitle": [], "attachment": [], "data": []}) for track in data.streams: if track.codec_type == "video" and ( track.get("disposition", {}).get("attached_pic") or track.get("tags", {}).get("MIMETYPE", "").startswith("image") ): streams.attachment.append(track) elif track.codec_type in streams: streams[track.codec_type].append(track) else: logger.error(f"Unknown codec: {track.codec_type}") if not streams.video: raise FlixError(f"There were no video streams detected: {data}") for stream in streams.video: if "bits_per_raw_sample" in stream: stream.bit_depth = int(stream.bits_per_raw_sample) else: stream.bit_depth = guess_bit_depth(stream.pix_fmt, stream.get("color_primaries")) app.fastflix.current_video.streams = streams app.fastflix.current_video.video_settings.selected_track = streams.video[0].index app.fastflix.current_video.format = data.format app.fastflix.current_video.duration = float(data.format.get("duration", 0))
def probe(app: FastFlixApp, file: Path) -> Box: """Run FFprobe on a file""" command = [ f"{app.fastflix.config.ffprobe}", "-v", "quiet", "-loglevel", "panic", "-print_format", "json", "-show_format", "-show_streams", f"{clean_file_string(file)}", ] result = execute(command) if result.returncode != 0: raise FlixError(f"Error code returned running FFprobe: {result.stdout} - {result.stderr}") if result.stdout.strip() == "{}": raise FlixError(f"No output from FFprobe, not a known video type. stderr: {result.stderr}") try: return Box.from_json(result.stdout) except BoxError: logger.error(f"Could not read output: {result.stdout} - {result.stderr}") raise FlixError(result.stderr)
def ffprobe_configuration(app, config: Config, **_): """Extract the version of ffprobe""" res = execute([f"{config.ffprobe}", "-version"]) if res.returncode != 0: raise FlixError(f'"{config.ffprobe}" file not found') try: version = res.stdout.split(" ", 4)[2] except (ValueError, IndexError): raise FlixError(f'Cannot parse version of ffprobe from "{res.stdout}"') app.fastflix.ffprobe_version = version
def ffmpeg_configuration(app, config: Config, **_): """Extract the version and libraries available from the specified version of FFmpeg""" res = execute([f"{config.ffmpeg}", "-version"]) if res.returncode != 0: raise FlixError(f'"{config.ffmpeg}" file not found') config = [] try: version = res.stdout.split(" ", 4)[2] except (ValueError, IndexError): raise FlixError(f'Cannot parse version of ffmpeg from "{res.stdout}"') line_denote = "configuration: " for line in res.stdout.split("\n"): if line.startswith(line_denote): config = [x[9:].strip() for x in line[len(line_denote) :].split(" ") if x.startswith("--enable")] app.fastflix.ffmpeg_version = version app.fastflix.ffmpeg_config = config
def s(a, v, base=50_000): upper, lower = [int(x) for x in a.get(v, "0/0").split("/")] if lower != base: upper *= base / lower value = int(upper) if value < 0 or value > 4_294_967_295: # 32-bit unsigned int max size raise FlixError("HDR value outside expected range") return value
def get_concat_item(file, location=0): all_items = get_all_concat_items(file) if not all_items: raise FlixError("concat file must start with `file` on each line.") if location == 0: return all_items[0] section = len(all_items) // 10 item_num = int((location * section)) - 1 if item_num >= len(all_items): return all_items[-1] return all_items[item_num]
def get_all_concat_items(file): items = [] with open(file) as f: for line in f: if line.strip().startswith("#"): continue elif line.strip().startswith("file"): filename = Path(line.strip()[5:].strip("'\"")) if not filename.exists(): raise FlixError(f'No file "{filename}" exists') items.append(filename) return items
def parse(app: FastFlixApp, **_): data = probe(app, app.fastflix.current_video.source) if "streams" not in data: raise FlixError("Not a video file") streams = Box({ "video": [], "audio": [], "subtitle": [], "attachment": [], "data": [] }) covers = [] for track in data.streams: if track.codec_type == "video" and track.get("disposition", {}).get("attached_pic"): streams.attachment.append(track) elif track.codec_type in streams: streams[track.codec_type].append(track) else: logger.error(f"Unknown codec: {track.codec_type}") if not streams.video: raise FlixError("There were no video streams detected") for stream in streams.video: if "bits_per_raw_sample" in stream: stream.bit_depth = int(stream.bits_per_raw_sample) else: stream.bit_depth = guess_bit_depth(stream.pix_fmt, stream.get("color_primaries")) app.fastflix.current_video.streams = streams app.fastflix.current_video.video_settings.selected_track = streams.video[ 0].index app.fastflix.current_video.width, app.fastflix.current_video.height = determine_rotation( streams) app.fastflix.current_video.format = data.format app.fastflix.current_video.duration = float(data.format.get("duration", 0))
def probe(app: FastFlixApp, file: Path) -> Box: """ Run FFprobe on a file """ command = [ f"{app.fastflix.config.ffprobe}", "-v", "quiet", "-loglevel", "panic", "-print_format", "json", "-show_format", "-show_streams", f"{file}", ] result = execute(command) try: return Box.from_json(result.stdout) except BoxError: logger.error( f"Could not read output: {result.stdout} - {result.stderr}") raise FlixError(result.stderr)
def get_first_concat_item(file): all_items = get_all_concat_items(file) if not all_items: raise FlixError("concat file must start with `file` on each line.") return all_items[0]