Skip to content

Commit

Permalink
add corrupted video detection and re encode function to potential fix…
Browse files Browse the repository at this point in the history
… broken video
  • Loading branch information
07pepa committed Feb 4, 2025
1 parent 52329a1 commit 64ba22e
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 0 deletions.
Binary file added media/no_duration.webm
Binary file not shown.
3 changes: 3 additions & 0 deletions moviepy/video/io/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class VideoCorruptedError(RuntimeError):
"""Error raised when a video file is corrupted."""
pass
7 changes: 7 additions & 0 deletions moviepy/video/io/ffmpeg_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
cross_platform_popen_params,
ffmpeg_escape_filename,
)
from moviepy.video.io.errors import VideoCorruptedError


class FFMPEG_VideoReader:
Expand Down Expand Up @@ -760,7 +761,11 @@ def parse_duration(self, line):
r"([0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9])",
time_raw_string,
)
if match_duration is None:
raise VideoCorruptedError("Video is corrupted and missing duration")
return convert_to_seconds(match_duration.group(1))
except VideoCorruptedError:
raise
except Exception:
raise IOError(
(
Expand Down Expand Up @@ -883,6 +888,8 @@ def ffmpeg_parse_infos(
check_duration=check_duration,
decode_file=decode_file,
).parse()
except VideoCorruptedError as exc:
raise exc
except Exception as exc:
if os.path.isdir(filename):
raise IsADirectoryError(f"'{filename}' is a directory")
Expand Down
40 changes: 40 additions & 0 deletions moviepy/video/re_encode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import subprocess
from pathlib import Path

def reencode_video(input_file:str|Path, output_file:str|Path):
"""
Re-encode a video file using ffmpeg. (this may fix some issues with corrupted video file)
Parameters
----------
input_file : str or Path file that will be re-encoded
output_file: str or Path file where the re-encoded video will be saved
Returns
-------
"""
# Convert input and output files to Path objects
input_path = Path(input_file).resolve()
output_path = Path(output_file).resolve()

# Check if the input file exists
if not input_path.exists():
raise FileNotFoundError(f"Input file '{input_file}' not found.")

if output_path.exists():
raise FileExistsError(f"Output file '{output_file}' already exists.")

try:
# Construct the ffmpeg command
command = [
"ffmpeg",
"-i", str(input_path),
"-c", "copy", # Copy audio without re-encoding
str(output_path)
]

# Run the command
proc=subprocess.run(command, check=True)
proc.check_returncode()
except subprocess.CalledProcessError as e:
print(f"Error: ffmpeg command failed with error {e}")
16 changes: 16 additions & 0 deletions tests/test_VideoFileClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import copy
import os
from pathlib import Path
from tempfile import TemporaryDirectory

import pytest

from moviepy.video.compositing.CompositeVideoClip import clips_array
from moviepy.video.io.VideoFileClip import VideoFileClip
from moviepy.video.VideoClip import ColorClip
from moviepy.video.io.errors import VideoCorruptedError
from moviepy.video.re_encode import reencode_video


def test_setup(util):
Expand Down Expand Up @@ -98,5 +102,17 @@ def test_ffmpeg_transparency_mask(util):
video.close()


def test_no_duration_raise_io_error():
with pytest.raises(VideoCorruptedError,match="Video is corrupted and missing duration"):
VideoFileClip("media/no_duration.webm")


def test_no_duration_re_encode_can_be_opened():
with TemporaryDirectory() as temp_dir:
target=Path(temp_dir).joinpath("re_encoded.webm")
reencode_video("media/no_duration.webm",target)
VideoFileClip(target)


if __name__ == "__main__":
pytest.main()

0 comments on commit 64ba22e

Please sign in to comment.