Repository: Zulko/moviepy
Pull request #1781 (2022-05-30 16:55:14)

Parse FFMPEG streams with square brackets
Hikvisions CCTV recordings use custom codec which causes FFMPEG
to return stream "value" in square brackets.

Original FFMPEG issue:
https://trac.ffmpeg.org/ticket/3566

Sample files:
https://samples.ffmpeg.org/ffmpeg-bugs/trac/ticket4182/

<!--
Please tick when you have done these. They don't need to all be completed before the PR is submitted.
Delete them if they are not appropriate for this pull request.
-->
- [x] I have provided code that clearly demonstrates the bug and that only works correctly when applying this fix
- [x] I have added suitable tests demonstrating a fixed bug or new/changed feature to the test suite in `tests/`
- [x] I have properly documented new or changed features in the documentation or in the docstrings
- [x] I have properly explained unusual or unexpected code in the comments around it


coveralls: 
[![Coverage Status](https://coveralls.io/builds/49570459/badge)](https://coveralls.io/builds/49570459)

Coverage decreased (-0.3%) to 84.511% when pulling **050da40bf91eb481772cd1c56e32fae1ef842b02 on TraseeHQ:b/handle-hikvision-codec** into **025c3fea33d50ec0d85e1852b83aa69ec8cd2c7d on Zulko:master**.

B3QL: @mondeja can you take a look? This should be a quick one ;) Thanks!
B3QL: > Could you handle it, please? Also, would you mind to add a CHANGELOG entry? Thanks!
Done! Should I add an extra test for it or extend the `test_stream_square_brackets` will be sufficient?

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57d2f86..3d301eb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -62,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Fixed mono clips crashing when `audio_fadein` FX applied [\#1574](https://github.com/Zulko/moviepy/pull/1574)
 - Fixed mono clips crashing when `audio_fadeout` FX applied [\#1578](https://github.com/Zulko/moviepy/pull/1578)
 - Fixed scroll FX not being scrolling [\#1591](https://github.com/Zulko/moviepy/pull/1591)
+- Fixed parsing FFMPEG streams with square brackets [\#1781](https://github.com/Zulko/moviepy/pull/1781)
 
 
 ## [v2.0.0.dev2](https://github.com/zulko/moviepy/tree/v2.0.0.dev2) (2020-10-05)
diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py
index e961017..9dbc2c5 100644
--- a/moviepy/video/io/ffmpeg_reader.py
+++ b/moviepy/video/io/ffmpeg_reader.py
@@ -422,7 +422,7 @@ class FFmpegInfosParser:
 
                 # get input number, stream number, language and type
                 main_info_match = re.search(
-                    r"^Stream\s#(\d+):(\d+)\(?(\w+)?\)?:\s(\w+):", line.lstrip()
+                    r"^Stream\s#(\d+):(\d+)[\[(]?(\w+)?[\])]?:\s(\w+):", line.lstrip()
                 )
                 (
                     input_number,
@@ -434,12 +434,16 @@ class FFmpegInfosParser:
                 stream_number = int(stream_number)
                 stream_type_lower = stream_type.lower()
 
+                if language is not None:
+                    if language == "und" or language.startswith("0x"):
+                        language = None
+
                 # start builiding the current stream
                 self._current_stream = {
                     "input_number": input_number,
                     "stream_number": stream_number,
                     "stream_type": stream_type_lower,
-                    "language": language if language != "und" else None,
+                    "language": language,
                     "default": not self._default_stream_found
                     or line.endswith("(default)"),
                 }


Test patch:
diff --git a/tests/test_ffmpeg_reader.py b/tests/test_ffmpeg_reader.py
index a1a5cce..89365ba 100644
--- a/tests/test_ffmpeg_reader.py
+++ b/tests/test_ffmpeg_reader.py
@@ -511,6 +511,22 @@ At least one output file must be specified"""
     assert len(d["inputs"][0]["streams"]) == 1
 
 
+def test_stream_square_brackets():
+    infos = """
+Input #0, mpeg, from 'clip.mp4':
+  Duration: 00:02:15.00, start: 52874.498178, bitrate: 266 kb/s
+    Stream #0:0[0x1e0]: Video: ..., 25 tbr, 90k tbn, 50 tbc
+    Stream #0:1[0x1c0]: Audio: mp2, 0 channels, s16p
+At least one output file must be specified"""
+
+    d = FFmpegInfosParser(infos, "clip.mp4").parse()
+
+    assert d
+    assert len(d["inputs"][0]["streams"]) == 2
+    assert d["inputs"][0]["streams"][0]["language"] is None
+    assert d["inputs"][0]["streams"][1]["language"] is None
+
+
 def test_sequential_frame_pos():
     """test_video.mp4 contains 5 frames at 1 fps.
     Each frame is 1x1 pixels and the sequence is Red, Green, Blue, Black, White.