Skip to content

Commit

Permalink
FFMPEG_VideoReader: set default video and audio stream automatically,…
Browse files Browse the repository at this point in the history
… if ffmpeg doesn't specify them. Fix Zulko#869 Zulko#899
  • Loading branch information
bzczb committed Jan 27, 2023
1 parent ade856f commit 31ef30b
Showing 1 changed file with 32 additions and 21 deletions.
53 changes: 32 additions & 21 deletions moviepy/video/io/ffmpeg_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,9 @@ def _reset_state(self):
# should be ignored
self._inside_output = False

# flag which indicates that a default stream has not been found yet
self._default_stream_found = False
# map from stream type to default stream
# if a default stream is not indicated, pick the first one available
self._default_streams = {}

# current input file, stream and chapter, which will be built at runtime
self._current_input_file = {"streams": []}
Expand Down Expand Up @@ -422,16 +423,13 @@ def parse(self):
"stream_number": stream_number,
"stream_type": stream_type_lower,
"language": language if language != "und" else None,
"default": not self._default_stream_found
or line.endswith("(default)"),
"default": (stream_type_lower not in self._default_streams) and line.endswith("(default)"),
}
self._default_stream_found = True

# for default streams, set their numbers globally, so it's
# easy to get without iterating all
if self._current_stream["default"]:
result[f"default_{stream_type_lower}_input_number"] = input_number
result[f"default_{stream_type_lower}_stream_number"] = stream_number
self._default_streams[stream_type_lower] = self._current_stream

# exit chapter
if self._current_chapter:
Expand All @@ -456,15 +454,14 @@ def parse(self):

# parse relevant data by stream type
try:
global_data, stream_data = self.parse_data_by_stream_type(
stream_data = self.parse_data_by_stream_type(
stream_type, line
)
except NotImplementedError as exc:
warnings.warn(
f"{str(exc)}\nffmpeg output:\n\n{self.infos}", UserWarning
)
else:
result.update(global_data)
self._current_stream.update(stream_data)
elif line.startswith(" Metadata:"):
# enter group " Metadata:"
Expand Down Expand Up @@ -522,6 +519,27 @@ def parse(self):
]
result["inputs"].append(self._current_input_file)

# set any missing default automatically
for stream in self._current_input_file["streams"]:
if stream["stream_type"] not in self._default_streams:
self._default_streams[stream["stream_type"]] = stream
stream["default"] = True

# set some global info based on the defaults
for stream_type_lower, stream_data in self._default_streams.items():
result[f"default_{stream_type_lower}_input_number"] = stream_data["input_number"]
result[f"default_{stream_type_lower}_stream_number"] = stream_data["stream_number"]

if stream_type_lower == "audio":
result["audio_found"] = True
result["audio_fps"] = stream_data["fps"]
result["audio_bitrate"] = stream_data["bitrate"]
elif stream_type_lower == "video":
result["video_found"] = True
result["video_size"] = stream_data.get("size", None)
result["video_bitrate"] = stream_data.get("bitrate", None)
result["video_fps"] = stream_data["fps"]

# some video duration utilities
if result["video_found"] and self.check_duration:
result["video_n_frames"] = int(result["duration"] * result["video_fps"])
Expand All @@ -543,7 +561,7 @@ def parse_data_by_stream_type(self, stream_type, line):
return {
"Audio": self.parse_audio_stream_data,
"Video": self.parse_video_stream_data,
"Data": lambda _line: ({}, {}),
"Data": lambda _line: {},
}[stream_type](line)
except KeyError:
raise NotImplementedError(
Expand All @@ -553,7 +571,7 @@ def parse_data_by_stream_type(self, stream_type, line):

def parse_audio_stream_data(self, line):
"""Parses data from "Stream ... Audio" line."""
global_data, stream_data = ({"audio_found": True}, {})
stream_data = {}
try:
stream_data["fps"] = int(re.search(r" (\d+) Hz", line).group(1))
except (AttributeError, ValueError):
Expand All @@ -564,14 +582,11 @@ def parse_audio_stream_data(self, line):
stream_data["bitrate"] = (
int(match_audio_bitrate.group(1)) if match_audio_bitrate else None
)
if self._current_stream["default"]:
global_data["audio_fps"] = stream_data["fps"]
global_data["audio_bitrate"] = stream_data["bitrate"]
return (global_data, stream_data)
return stream_data

def parse_video_stream_data(self, line):
"""Parses data from "Stream ... Video" line."""
global_data, stream_data = ({"video_found": True}, {})
stream_data = {}

try:
match_video_size = re.search(r" (\d+)x(\d+)[,\s]", line)
Expand Down Expand Up @@ -623,11 +638,7 @@ def parse_video_stream_data(self, line):
fps = x * coef
stream_data["fps"] = fps

if self._current_stream["default"]:
global_data["video_size"] = stream_data.get("size", None)
global_data["video_bitrate"] = stream_data.get("bitrate", None)
global_data["video_fps"] = stream_data["fps"]
return (global_data, stream_data)
return stream_data

def parse_fps(self, line):
return float(re.search(r" (\d+.?\d*) fps", line).group(1))
Expand Down

0 comments on commit 31ef30b

Please sign in to comment.