Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ffmpeg_reader not selecting a default stream #2114

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/soundtrack.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""A simple test script on how to put a soundtrack to a movie."""

from moviepy import *


Expand Down
1 change: 1 addition & 0 deletions moviepy/Clip.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Implements the central object of MoviePy, the Clip, and all the methods that
are common to the two subclasses of Clip, VideoClip and AudioClip.
"""

import copy as _copy
from functools import reduce
from numbers import Real
Expand Down
1 change: 1 addition & 0 deletions moviepy/audio/fx/all/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Use the fx method directly from the clip instance (e.g. ``clip.audio_normalize(...)``)
or import the function from moviepy.audio.fx instead.
"""

import warnings

from .. import * # noqa 401,F403
Expand Down
1 change: 1 addition & 0 deletions moviepy/decorators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Decorators used by moviepy."""

import inspect
import os

Expand Down
1 change: 1 addition & 0 deletions moviepy/tools.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Misc. useful functions that can be used at many places in the program."""

import os
import subprocess as sp
import warnings
Expand Down
1 change: 1 addition & 0 deletions moviepy/video/fx/all/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Use the fx method directly from the clip instance (e.g. ``clip.resize(...)``)
or import the function from moviepy.video.fx instead.
"""

import warnings

from moviepy.video.fx import * # noqa F401,F403
Expand Down
72 changes: 40 additions & 32 deletions moviepy/video/io/ffmpeg_reader.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Implements all the functions to read a video or a picture using ffmpeg."""

import os
import re
import subprocess as sp
Expand Down Expand Up @@ -343,8 +344,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 @@ -443,20 +445,14 @@ def parse(self):
"stream_number": stream_number,
"stream_type": stream_type_lower,
"language": language,
"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"]:
self.result[
f"default_{stream_type_lower}_input_number"
] = input_number
self.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 @@ -475,21 +471,18 @@ def parse(self):
input_number
]

# add new input file to self.result
# add new input file to result
self.result["inputs"].append(self._current_input_file)
self._current_input_file = {"input_number": input_number}

# parse relevant data by stream type
try:
global_data, stream_data = self.parse_data_by_stream_type(
stream_type, line
)
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:
self.result.update(global_data)
self._current_stream.update(stream_data)
elif line.startswith(" Metadata:"):
# enter group " Metadata:"
Expand Down Expand Up @@ -552,7 +545,7 @@ def parse(self):
self._last_metadata_field_added = field
self._current_chapter["metadata"][field] = value

# last input file, must be included in self.result
# last input file, must be included in the result
if self._current_input_file:
self._current_input_file["streams"].append(self._current_stream)
# include their chapters, if there are
Expand All @@ -562,6 +555,31 @@ def parse(self):
]
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():
self.result[f"default_{stream_type_lower}_input_number"] = stream_data[
"input_number"
]
self.result[f"default_{stream_type_lower}_stream_number"] = stream_data[
"stream_number"
]

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

# some video duration utilities
if self.result["video_found"] and self.check_duration:
self.result["video_n_frames"] = int(
Expand Down Expand Up @@ -599,7 +617,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 @@ -609,7 +627,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 @@ -620,14 +638,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 @@ -679,14 +694,7 @@ def parse_video_stream_data(self, line):
fps = x * coef
stream_data["fps"] = fps

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

return (global_data, stream_data)
return stream_data

def parse_fps(self, line):
"""Parses number of FPS from a line of the ``ffmpeg -i`` command output."""
Expand Down
1 change: 1 addition & 0 deletions moviepy/video/tools/credits.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Contains different functions to make end and opening credits, even though it is
difficult to fill everyone needs in this matter.
"""

from moviepy.decorators import convert_path_to_string
from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
from moviepy.video.fx.resize import resize
Expand Down
1 change: 1 addition & 0 deletions tests/test_VideoFileClip.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Video file clip tests meant to be run with pytest."""

import copy
import os

Expand Down