From c204a47a992627bfce37322a0c77c2cfc6f2e9e2 Mon Sep 17 00:00:00 2001 From: TheSpaceSheep Date: Fri, 26 Apr 2024 09:51:56 +0200 Subject: [PATCH 1/4] (fix) escape dashes in the beginning of filenames in ffmpeg commands --- moviepy/audio/io/ffmpeg_audiowriter.py | 6 +++--- moviepy/audio/io/readers.py | 6 +++--- moviepy/tools.py | 23 ++++++++++++++++++++ moviepy/video/io/ffmpeg_reader.py | 4 ++-- moviepy/video/io/ffmpeg_tools.py | 29 ++++++++++++++++---------- moviepy/video/io/ffmpeg_writer.py | 8 +++---- moviepy/video/io/gif_writers.py | 6 +++--- 7 files changed, 56 insertions(+), 26 deletions(-) diff --git a/moviepy/audio/io/ffmpeg_audiowriter.py b/moviepy/audio/io/ffmpeg_audiowriter.py index 60c2de508..d1d41b24d 100644 --- a/moviepy/audio/io/ffmpeg_audiowriter.py +++ b/moviepy/audio/io/ffmpeg_audiowriter.py @@ -6,7 +6,7 @@ from moviepy.config import FFMPEG_BINARY from moviepy.decorators import requires_duration -from moviepy.tools import cross_platform_popen_params +from moviepy.tools import cross_platform_popen_params, dash_escape class FFMPEG_AudioWriter: @@ -74,7 +74,7 @@ def __init__( if input_video is None: cmd.extend(["-vn"]) else: - cmd.extend(["-i", input_video, "-vcodec", "copy"]) + cmd.extend(["-i", dash_escape(input_video), "-vcodec", "copy"]) cmd.extend(["-acodec", codec] + ["-ar", "%d" % fps_input]) cmd.extend(["-strict", "-2"]) # needed to support codec 'aac' @@ -82,7 +82,7 @@ def __init__( cmd.extend(["-ab", bitrate]) if ffmpeg_params is not None: cmd.extend(ffmpeg_params) - cmd.extend([filename]) + cmd.extend([dash_escape(filename)]) popen_params = cross_platform_popen_params( {"stdout": sp.DEVNULL, "stderr": logfile, "stdin": sp.PIPE} diff --git a/moviepy/audio/io/readers.py b/moviepy/audio/io/readers.py index 4f60efb44..42997ee40 100644 --- a/moviepy/audio/io/readers.py +++ b/moviepy/audio/io/readers.py @@ -6,7 +6,7 @@ import numpy as np from moviepy.config import FFMPEG_BINARY -from moviepy.tools import cross_platform_popen_params +from moviepy.tools import cross_platform_popen_params, dash_escape from moviepy.video.io.ffmpeg_reader import ffmpeg_parse_infos @@ -82,13 +82,13 @@ def initialize(self, start_time=0): "-ss", "%.05f" % (start_time - offset), "-i", - self.filename, + dash_escape(self.filename), "-vn", "-ss", "%.05f" % offset, ] else: - i_arg = ["-i", self.filename, "-vn"] + i_arg = ["-i", dash_escape(self.filename), "-vn"] cmd = ( [FFMPEG_BINARY] diff --git a/moviepy/tools.py b/moviepy/tools.py index a260e75bf..6e6a4fbdb 100644 --- a/moviepy/tools.py +++ b/moviepy/tools.py @@ -168,3 +168,26 @@ def find_extension(codec): "specify a temp_audiofile with the right extension " "in write_videofile." ) + + +def dash_escape(path): + """Makes paths that start with dash '-' intertreptable as command line arguments + + Returns + ------- + + - ./path if path starts with dash '-' + - path otherwise + + Examples + -------- + >>> dash_escape('-filenamethatstartswithdash-.mp4') + './-filenamethatstartswithdash-.mp4' + >>> dash_escape('-path/that/starts/with/dash.mp4') + './-path/that/starts/with/dash-.mp4' + >>> dash_escape('file-name-.mp4') + 'file-name-.mp4' + >>> dash_escape('/absolute/path/to/-file.mp4') + '/absolute/path/to/-file.mp4' + """ + return "./" * (path[0] == "-") + path diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py index b7be97177..dad2509b6 100644 --- a/moviepy/video/io/ffmpeg_reader.py +++ b/moviepy/video/io/ffmpeg_reader.py @@ -7,7 +7,7 @@ import numpy as np from moviepy.config import FFMPEG_BINARY # ffmpeg, ffmpeg.exe, etc... -from moviepy.tools import convert_to_seconds, cross_platform_popen_params +from moviepy.tools import convert_to_seconds, cross_platform_popen_params, dash_escape class FFMPEG_VideoReader: @@ -790,7 +790,7 @@ def ffmpeg_parse_infos( https://github.com/Zulko/moviepy/pull/1222). """ # Open the file in a pipe, read output - cmd = [FFMPEG_BINARY, "-hide_banner", "-i", filename] + cmd = [FFMPEG_BINARY, "-hide_banner", "-i", dash_escape(filename)] if decode_file: cmd.extend(["-f", "null", "-"]) diff --git a/moviepy/video/io/ffmpeg_tools.py b/moviepy/video/io/ffmpeg_tools.py index 610a8cc9c..2a92670d1 100644 --- a/moviepy/video/io/ffmpeg_tools.py +++ b/moviepy/video/io/ffmpeg_tools.py @@ -4,7 +4,7 @@ from moviepy.config import FFMPEG_BINARY from moviepy.decorators import convert_parameter_to_seconds, convert_path_to_string -from moviepy.tools import subprocess_call +from moviepy.tools import dash_escape, subprocess_call @convert_path_to_string(("inputfile", "outputfile")) @@ -41,7 +41,7 @@ def ffmpeg_extract_subclip( "-ss", "%0.2f" % start_time, "-i", - inputfile, + dash_escape(inputfile), "-t", "%0.2f" % (end_time - start_time), "-map", @@ -51,7 +51,7 @@ def ffmpeg_extract_subclip( "-acodec", "copy", "-copyts", - outputfile, + dash_escape(outputfile), ] subprocess_call(cmd, logger=logger) @@ -89,14 +89,14 @@ def ffmpeg_merge_video_audio( FFMPEG_BINARY, "-y", "-i", - audiofile, + dash_escape(audiofile), "-i", - videofile, + dash_escape(videofile), "-vcodec", video_codec, "-acodec", audio_codec, - outputfile, + dash_escape(outputfile), ] subprocess_call(cmd, logger=logger) @@ -125,12 +125,12 @@ def ffmpeg_extract_audio(inputfile, outputfile, bitrate=3000, fps=44100, logger= FFMPEG_BINARY, "-y", "-i", - inputfile, + dash_escape(inputfile), "-ab", "%dk" % bitrate, "-ar", "%d" % fps, - outputfile, + dash_escape(outputfile), ] subprocess_call(cmd, logger=logger) @@ -154,10 +154,10 @@ def ffmpeg_resize(inputfile, outputfile, size, logger="bar"): cmd = [ FFMPEG_BINARY, "-i", - inputfile, + dash_escape(inputfile), "-vf", "scale=%d:%d" % (size[0], size[1]), - outputfile, + dash_escape(outputfile), ] subprocess_call(cmd, logger=logger) @@ -194,7 +194,14 @@ def ffmpeg_stabilize_video( outputfile = f"{name}_stabilized{ext}" outputfile = os.path.join(output_dir, outputfile) - cmd = [FFMPEG_BINARY, "-i", inputfile, "-vf", "deshake", outputfile] + cmd = [ + FFMPEG_BINARY, + "-i", + dash_escape(inputfile), + "-vf", + "deshake", + dash_escape(outputfile), + ] if overwrite_file: cmd.append("-y") subprocess_call(cmd, logger=logger) diff --git a/moviepy/video/io/ffmpeg_writer.py b/moviepy/video/io/ffmpeg_writer.py index 9319850cb..9d50ac16c 100644 --- a/moviepy/video/io/ffmpeg_writer.py +++ b/moviepy/video/io/ffmpeg_writer.py @@ -9,7 +9,7 @@ from proglog import proglog from moviepy.config import FFMPEG_BINARY -from moviepy.tools import cross_platform_popen_params +from moviepy.tools import cross_platform_popen_params, dash_escape class FFMPEG_VideoWriter: @@ -119,7 +119,7 @@ def __init__( "-", ] if audiofile is not None: - cmd.extend(["-i", audiofile, "-acodec", "copy"]) + cmd.extend(["-i", dash_escape(audiofile), "-acodec", "copy"]) cmd.extend(["-vcodec", codec, "-preset", preset]) if ffmpeg_params is not None: cmd.extend(ffmpeg_params) @@ -131,7 +131,7 @@ def __init__( if (codec == "libx264") and (size[0] % 2 == 0) and (size[1] % 2 == 0): cmd.extend(["-pix_fmt", "yuv420p"]) - cmd.extend([filename]) + cmd.extend([dash_escape(filename)]) popen_params = cross_platform_popen_params( {"stdout": sp.DEVNULL, "stderr": logfile, "stdin": sp.PIPE} @@ -307,7 +307,7 @@ def ffmpeg_write_image(filename, image, logfile=False, pixel_format=None): pixel_format, "-i", "-", - filename, + dash_escape(filename), ] if logfile: diff --git a/moviepy/video/io/gif_writers.py b/moviepy/video/io/gif_writers.py index f4240139b..8c7ad2515 100644 --- a/moviepy/video/io/gif_writers.py +++ b/moviepy/video/io/gif_writers.py @@ -8,7 +8,7 @@ from moviepy.config import FFMPEG_BINARY, IMAGEMAGICK_BINARY from moviepy.decorators import requires_duration, use_clip_fps_by_default -from moviepy.tools import cross_platform_popen_params, subprocess_call +from moviepy.tools import cross_platform_popen_params, dash_escape, subprocess_call from moviepy.video.fx.loop import loop as loop_fx @@ -153,7 +153,7 @@ def write_gif_with_tempfiles( "-r", str(fps), "-i", - file_root + "_GIFTEMP%04d.png", + dash_escape(file_root) + "_GIFTEMP%04d.png", "-r", str(fps), filename, @@ -328,7 +328,7 @@ def write_gif( (pixel_format), "-r", "%.02f" % fps, - filename, + dash_escape(filename), ], **popen_params, ) From 34cc7ad352c73e8d29a7c34cd81419a4b67b0da9 Mon Sep 17 00:00:00 2001 From: TheSpaceSheep Date: Fri, 26 Apr 2024 10:43:04 +0200 Subject: [PATCH 2/4] fix typo in docstring --- moviepy/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moviepy/tools.py b/moviepy/tools.py index 6e6a4fbdb..35556cd48 100644 --- a/moviepy/tools.py +++ b/moviepy/tools.py @@ -184,7 +184,7 @@ def dash_escape(path): >>> dash_escape('-filenamethatstartswithdash-.mp4') './-filenamethatstartswithdash-.mp4' >>> dash_escape('-path/that/starts/with/dash.mp4') - './-path/that/starts/with/dash-.mp4' + './-path/that/starts/with/dash.mp4' >>> dash_escape('file-name-.mp4') 'file-name-.mp4' >>> dash_escape('/absolute/path/to/-file.mp4') From c7a14227c1c30ef287493e2c2cac2e50e775e0ae Mon Sep 17 00:00:00 2001 From: TheSpaceSheep Date: Fri, 26 Apr 2024 10:51:20 +0200 Subject: [PATCH 3/4] (chore) write tests for dash_escape, and to demonstrate that the issue has been fixed --- media/-filenamethatstartswithdash-.mp4 | Bin 0 -> 1687 bytes tests/test_issues.py | 10 ++++++++++ tests/test_tools.py | 14 ++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 media/-filenamethatstartswithdash-.mp4 diff --git a/media/-filenamethatstartswithdash-.mp4 b/media/-filenamethatstartswithdash-.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..ad89c0e66f83f75cd9d3417e3e06577a41186e9a GIT binary patch literal 1687 zcmZuyUuYaf7@xZkDMe5*g%lE)U@aDt%kCzZrqRWmp){gJMJyu1GPgT>yWIZE%;a)c z!KM<*D$n)aFLXB#AZ9e0M(Zjcaj#B}E@d5uDF6={oi#GKNAM zuh8wd0Ch)TrSS4F)Y7=K+(ZfsNGHkjwumtC_Ix^teQagdVew3rDf~DUhPDxBMyf=B z1b;JGRifwld0L2aL#U8ZBF-QIq7uXjl}M#Vt(soZ1C9gN0TzKM36->ypWmK$_qpX&H_u@vIlL~sareHOXCb=?aNI1t2m=YyHtV-s-%vvTKV3+ z(^6dduCGF#Hc?eN6(+7TQ?vLAoyE0kbAScumZRvpbEn|YMiwYxT;PiE9B_ls5-YfU zx=Xf>_j^=c+F(ce+y7nPmRDc?^o8o?h3b35bN{SAf#7Ikb0TSYzrO!!_2che?~ZKU z`sMD4Z=x%-aICTU;`0+%PHY_MQ~cV8<7Y0O{r;0XKYn(6{37BbjZOch+m9W8qk4Ei z_$t4!J@9Yb=f9rnK0csYn&%zFle7~B828!GAB;VDiB%Z=?b&@3xDU)1_m~euReY>( zq3Ue}-!bhn=g>aljvDeFv;%n_nwK~O-Z2>%joFw?G=3mTLxgnKcQ}=sX%GuUcp4l$ z`yx1t?9ChHx~V8gREf~hr@xD7JV!qT%OVRBNu0{Ncn%@JNGB~t2iqYgW5MejAZ*YcRdxh3`mXaLB_zp*N^sb{mh2y zQsrNExPO}+ZDG88?b*8<6Nm6JSlK<^y*&AKg$+k{e!D^9quP{pB_T>O%-AB~5p0+0 zO-6odqy7Wjb-39WxIO{cE*Cl_eg?7gWn|F7U;PPi42`3W?lk1{b6_&ku+W;Tfe{$r xPGva)vWfk7hx=BdD5$*mY$_f+kf*;eISG#A9}0<(D0@Y8!uov8v1+zu{SPtNh!X$+ literal 0 HcmV?d00001 diff --git a/tests/test_issues.py b/tests/test_issues.py index 5a3dbaaf9..4d47475c9 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -9,6 +9,7 @@ from moviepy.video.compositing.concatenate import concatenate_videoclips from moviepy.video.compositing.transitions import crossfadein, crossfadeout from moviepy.video.fx.resize import resize +from moviepy.video.io.ffmpeg_reader import ffmpeg_parse_infos from moviepy.video.io.VideoFileClip import VideoFileClip from moviepy.video.VideoClip import ColorClip, ImageClip, VideoClip @@ -401,5 +402,14 @@ def test_issue_655(): assert True +def test_issue_2160(): + os.chdir("media") + try: + d = ffmpeg_parse_infos("-filenamethatstartswithdash-.mp4") + assert d["video_found"] + finally: # change back to working directory even if test fails + os.chdir("..") + + if __name__ == "__main__": pytest.main() diff --git a/tests/test_tools.py b/tests/test_tools.py index 552d29c4e..9f877b660 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -329,5 +329,19 @@ def test_decorators_argument_converters_consistency( assert function_data["function_arguments"] +@pytest.mark.parametrize( + "given, expected", + [ + ("-filenamethatstartswithdash-.mp4", "./-filenamethatstartswithdash-.mp4"), + ("-path/that/starts/with/dash.mp4", "./-path/that/starts/with/dash.mp4"), + ("file-name-.mp4", "file-name-.mp4"), + ("/absolute/path/to/-file.mp4", "/absolute/path/to/-file.mp4"), + ], +) +def test_dash_escape(given, expected): + """Test the dash_escape function outputs correct paths as per the docstring.""" + assert tools.dash_escape(given) == expected + + if __name__ == "__main__": pytest.main() From 7216d6ea9695e247dd9b4255726e7cc7347bd30f Mon Sep 17 00:00:00 2001 From: TheSpaceSheep Date: Fri, 26 Apr 2024 10:56:31 +0200 Subject: [PATCH 4/4] (refactor) improve dash_escape readability --- moviepy/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moviepy/tools.py b/moviepy/tools.py index 35556cd48..1f1770728 100644 --- a/moviepy/tools.py +++ b/moviepy/tools.py @@ -190,4 +190,4 @@ def dash_escape(path): >>> dash_escape('/absolute/path/to/-file.mp4') '/absolute/path/to/-file.mp4' """ - return "./" * (path[0] == "-") + path + return "./" + path if path.startswith("-") else path