kkroening / ffmpeg-python

Python bindings for FFmpeg - with complex filtering support
Apache License 2.0
10.08k stars 893 forks source link

Error initializing filter 'subtitles' with args #745

Open X-SZM opened 1 year ago

X-SZM commented 1 year ago

Here's my python code:

import ffmpeg
input_dir = "C:/Users/27433/Desktop/1/"
video_file = '1_1_1.mp4'
subtl_file = '1_1_1.srt'

(
    ffmpeg
    .input(input_dir + video_file)
    .filter('subtitles', input_dir + subtl_file)
    .output(input_dir + 'output.mp4')
    .run()
)

I'm sure all the files are in the right place: video in "C:\Users\27433\Desktop\1\1_1_1.mp4", subtitle in "C:\Users\27433\Desktop\1\1_1_1.srt" And the srt file's encoding is UTF-8, But the ffmpeg reported error:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'C:/Users/27433/Desktop/1/1_1_1.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf57.71.100
    description     : Packed by Bilibili XCoder v2.0.2
  Duration: 00:00:27.73, start: 0.000000, bitrate: 231 kb/s
  Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], 45 kb/s, 30 fps, 30 tbr, 16k tbn (default)
    Metadata:
      handler_name    : VideoHandler
      vendor_id       : [0][0][0][0]
  Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 176 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
      vendor_id       : [0][0][0][0]
[Parsed_subtitles_0 @ 0000025a67d4d200] libass API version: 0x1502002
[Parsed_subtitles_0 @ 0000025a67d4d200] libass source: commit: 0.15.2-69-gf664ced049394e2a5d4300ba526e206df73ec729
[Parsed_subtitles_0 @ 0000025a67d4d200] Shaper: FriBidi 1.0.11 (SIMPLE) HarfBuzz-ng 4.2.0 (COMPLEX)
[Parsed_subtitles_0 @ 0000025a67d4d200] Unable to open C\:/Users/27433/Desktop/1/1_1_1.srt
[AVFilterGraph @ 0000025a67d6f280] Error initializing filter 'subtitles' with args 'C\\\:/Users/27433/Desktop/1/1_1_1.srt'
Error initializing complex filters.
Invalid argument
Traceback (most recent call last):
  File "C:\Users\27433\Desktop\Ermu1.5\test.py", line 11, in <module>
    .run()
  File "C:\Users\27433\Desktop\Ermu1.5\venv\lib\site-packages\ffmpeg\_run.py", line 325, in run
    raise Error('ffmpeg', out, err)
ffmpeg._run.Error: ffmpeg error (see stderr output for detail)

Then I tried to replace the "\"to "\"or"/",but the error are still there, And I also tried to use ``` file_path=r"C:\Users\27433\Desktop\1\1_1_1.mp4" subtitle_path=r"C:\Users\27433\Desktop\1\1_1_1.srt" output_path=r"C:\Users\27433\Desktop\1\output.mp4" os.system("ffmpeg.EXE -i {} -vf subtitles={} {}".format(file_path, subtitle_path, output_path))

(The ffmpeg.exe are in the same folder as the .py file)
But it also raise error:

[subtitles @ 0000027c04691ac0] Unable to parse option value "Users27433Desktop11_1_1.srt" as image size Last message repeated 1 times [subtitles @ 0000027c04691ac0] Error setting option original_size to value Users27433Desktop11_1_1.srt. [Parsed_subtitles_0 @ 0000027c04600600] Error applying options to the filter. [AVFilterGraph @ 0000027c091ac300] Error initializing filter 'subtitles' with args 'C:Users27433Desktop11_1_1.srt' Error reinitializing filters! Failed to inject frame into filter network: Invalid argument Error while processing the decoded data for stream #0:0 [aac @ 0000027c041d0bc0] Qavg: 11856.707 [aac @ 0000027c041d0bc0] 2 frames left in the queue on closing Conversion failed!

or replace the "\" to"\\"or"/"

[subtitles @ 0000018074536a80] Unable to parse option value "Users27433Desktop11_1_1.srt" as image size Last message repeated 1 times [subtitles @ 0000018074536a80] Error setting option original_size to value Users27433Desktop11_1_1.srt. [Parsed_subtitles_0 @ 0000018071885ec0] Error applying options to the filter. [AVFilterGraph @ 0000018076c88900] Error initializing filter 'subtitles' with args 'C:\Users\27433\Desktop\1\1_1_1.srt' Error reinitializing filters!

[subtitles @ 000002d7e9858ac0] Unable to parse option value "/Users/27433/Desktop/1/1_1_1.srt" as image size Last message repeated 1 times [subtitles @ 000002d7e9858ac0] Error setting option original_size to value /Users/27433/Desktop/1/1_1_1.srt. [Parsed_subtitles_0 @ 000002d7e9c96bc0] Error applying options to the filter. [AVFilterGraph @ 000002d7ef088a00] Error initializing filter 'subtitles' with args 'C:/Users/27433/Desktop/1/1_1_1.srt'


Why could all these happen?how should I fix it?
I'll be very grateful if anybody could help me.
ef1500 commented 1 year ago

It's because of filtergraph escaping. You can find more on the docs. Spent about an hour banging my head against a wall until I finally got it. This code is what got it for me:

path_converter = lambda path: path.replace("\\", "/").replace(":", "\:/").replace(" ", "\\ ").replace("(", "\\(").replace(")", "\\)").replace("[", "\\[").replace("]", "\\]").replace("'", "'\\''")
so instead of subtitles="subtitles.mkv" or whatever do subtitles=path_converter(path_to_subtitles)
ClawhammerLobotomy commented 1 year ago

@ef1500 I'm curious how you are using this together with the rest of your code. I'm trying to use your solution but not having any luck with mine.

My filename: [SubsPlease] 16bit Sensation - Another Layer - 01 (720p) [FDA8E9E6].mkv Using regular ffmpeg CLI, I need to escape only the brackets like so:

-vf subtitles="\[SubsPlease\] 16bit Sensation - Another Layer - 01 (720p) \[FDA8E9E6\].mkv"

However, I still see an error in ffmpeg-python trying your solution.

It seems to be related to loading the file from a different directory than CWD when using the full file path with drive included. I tested by changing the file to just file.mkv which caused the same issue in loading the filter.

Test 1 Code:

out_file = 'out.webm'
start_time = '00:01:18.400'
end_time = '00:01:29.750'
in_dir = 'I:/'
in_file = os.path.join(in_dir,'file.mkv')
in_put = ffmpeg.input(in_file,ss=start_time,to=end_time)
audio = in_put.audio
video = in_put.video.filter('subtitles',in_file,si=0)
out = ffmpeg.output(audio,video,filename=out_file,acodec='libopus',vcodec='vp9').overwrite_output()
ffmpeg.run(out)

Test 1 Error:

[Parsed_subtitles_0 @ 00000186a3a856c0] Unable to open I\:/file.mkv
[AVFilterGraph @ 00000186a26a7f80] Error initializing filters
Error initializing complex filters.
Invalid argument

Test 2 Code:

path_converter = lambda path: path.replace("\\", "/").replace(":", "\:/").replace(" ", "\\ ").replace("(", "\\(").replace(")", "\\)").replace("[", "\\[").replace("]", "\\]").replace("'", "'\\''")
out_file = 'out.webm'
start_time = '00:01:18.400'
end_time = '00:01:29.750'
in_dir = 'I:/'
in_file = os.path.join(in_dir,'file.mkv')
in_put = ffmpeg.input(in_file,ss=start_time,to=end_time)
audio = in_put.audio
video = in_put.video.filter('subtitles',path_converter(in_file),si=0)
out = ffmpeg.output(audio,video,filename=out_file,acodec='libopus',vcodec='vp9').overwrite_output()
ffmpeg.run(out)

Test 2 Error:

[Parsed_subtitles_0 @ 0000025b34ed4bc0] Unable to open I\\\://file.mkv
[AVFilterGraph @ 0000025b33af7e00] Error initializing filters
Error initializing complex filters.
Invalid argument

Test 3 Code: (file moved to CWD of python script)

out_file = 'out.webm'
start_time = '00:01:18.400'
end_time = '00:01:29.750'
in_file = 'file.mkv'
in_put = ffmpeg.input(in_file,ss=start_time,to=end_time)
audio = in_put.audio
video = in_put.video.filter('subtitles',in_file,si=0)
out = ffmpeg.output(audio,video,filename=out_file,acodec='libopus',vcodec='vp9').overwrite_output()
ffmpeg.run(out)

Test 3 Successful

For good measure, I also tested with my original filename in CWD. Test 4 Code:

out_file = 'out.webm'
start_time = '00:01:18.400'
end_time = '00:01:29.750'
in_file = '[SubsPlease] 16bit Sensation - Another Layer - 01 (720p) [FDA8E9E6].mkv'
in_put = ffmpeg.input(in_file,ss=start_time,to=end_time)
audio = in_put.audio
video = in_put.video.filter('subtitles',in_file,si=0)
out = ffmpeg.output(audio,video,filename=out_file,acodec='libopus',vcodec='vp9').overwrite_output()
ffmpeg.run(out)

Test 4 Successful

ef1500 commented 1 year ago

I was using it for a project which would allow me to clip videos for personal use. Sadly, due to my current circumstances, I am not in a position where I am near my project's code and won't be for a few months.

If my memory serves correctly, I think used the path converter to escape a full path from user input via radio webui. I was trying to hardcode the subtitles to my output clip and was unable to do it. I stumbled upon this issue, and after some additional searching, I found some related info on the docs (see here as well)

If you read the docs, it says: "A first level escaping affects the content of each filter option value, which may contain the special character : used to separate values, or one of the escaping characters \'.

A second level escaping affects the whole filter description, which may contain the escaping characters \' or the special characters [],; used by the filtergraph description.

Finally, when you specify a filtergraph on a shell commandline, you need to perform a third level escaping for the shell special characters contained within it."

If I had my code, I'd share a snippet, but as I said earlier, I can't access it for the time being. For now, this is really the best I can do to help you.

ClawhammerLobotomy commented 1 year ago

I appreciate the reply. Unfortunate that I can't see your example, but I'm still trying to get this working.

For now, I think I'm resorting to using a symbolic link created on the fly so I don't need to work with drives in the path.

@X-SZM I am wondering if your issue is also due to the full drive path in your subtitles filter? Have you tested with running the python script from the same folder as your inputs and using relative paths?

EX with removed input directory:

import ffmpeg

video_file = '1_1_1.mp4'
subtl_file = '1_1_1.srt'

(
    ffmpeg
    .input(video_file)
    .filter('subtitles',  subtl_file)
    .output('output.mp4')
    .run()
)

This is what I'm going to be using until I can figure this out: (Or maybe this will just end up being my solution)

import os
import ffmpeg

out_file = 'out.webm'
start_time = '00:01:18.400'
end_time = '00:01:29.750'

in_dir = 'I:/'
in_file = os.path.join(in_dir,'file.mkv')
in_link = 'file.mkv'

if not os.path.isfile(in_link):
    os.symlink(in_file, in_link)

input_vid = ffmpeg.input(in_link)
vid = (
    input_vid
    .filter_('subtitles',in_link)
    .trim(start=start_time,end=end_time)
    .setpts('PTS-STARTPTS')
)
aud = (
    input_vid
    .filter_('atrim',start=start_time,end=end_time)
    .filter_('asetpts', 'PTS-STARTPTS')
)
joined = ffmpeg.concat(vid, aud, v=1, a=1).node
output = ffmpeg.output(joined['v'], joined['a'], filename=out_file,acodec='libopus',vcodec='vp9').overwrite_output()
output.run()

if os.path.isfile(in_link):
    os.remove(in_link)
ClawhammerLobotomy commented 1 year ago

The escaping for subtitle filters seems overzealous with the drive path. Unsure if it affects other filters, but I had no ill effects from my testing with my other FilterNodes.

The output of the FilterNode class for .filter('subtitles','I:/file.mkv') results in subtitles=I\\\\\\:/file.mkv being passed to ffmpeg.

However, this is causing one extra backslash to be passed to the program as seen in my errors. [Parsed_subtitles_1 @ 000001f0025056c0] Unable to open I\:/file.mkv

I removed the last escape_chars call from this class and everything worked as expected. subtitles=I\\\:/file.mkv is being passed to ffmpeg with no errors.

No amount of manual pre-escaping was able to get me a successful result with the existing code.

Would be nice to be able to omit escaping like #358 suggests. I think I will stick with my workaround of using symlink creation. Hope this info is helpful to someone that may have this same issues.

iorilu commented 10 months ago

I was using it for a project which would allow me to clip videos for personal use. Sadly, due to my current circumstances, I am not in a position where I am near my project's code and won't be for a few months.

@ef1500 hi , I also had similar problem , can you share some full working code , thanks . I tried use the funtion path_converter you methoned , but still not work.

voidpenguin-28 commented 2 months ago

The escaping for subtitle filters seems overzealous with the drive path. Unsure if it affects other filters, but I had no ill effects from my testing with my other FilterNodes.

The output of the FilterNode class for .filter('subtitles','I:/file.mkv') results in subtitles=I\\\\\\:/file.mkv being passed to ffmpeg.

However, this is causing one extra backslash to be passed to the program as seen in my errors. [Parsed_subtitles_1 @ 000001f0025056c0] Unable to open I\:/file.mkv

I removed the last escape_chars call from this class and everything worked as expected. subtitles=I\\\:/file.mkv is being passed to ffmpeg with no errors.
For quick reference for any others facing this issue, here's a simple way to implement this solution:


import ffmpeg

### override filter code

def _new_get_filter(self, outgoing_edges):
    if self.name == 'subtitles' or self.name == 'ass':
        path = ffmpeg._utils.escape_chars(self.args[0], '\\\'=:')
        return f"{self.name}='{path}'"
    else:
        return self._orig_get_filter(outgoing_edges)

setattr(ffmpeg.nodes.FilterNode, '_orig_get_filter', ffmpeg.nodes.FilterNode.__dict__["_get_filter"])
setattr(ffmpeg.nodes.FilterNode, '_get_filter', _new_get_filter)

### now use ffmpeg as normal

input_path = 'C:\\test_video[0].mkv'
output_path = 'C:\\test_video_output.mkv'

ffmpeg.input(input_path).filter('subtitles', input_path).output(
    output_path, preset='veryfast', map='0:a').run(overwrite_output=True)
zh7i commented 1 month ago

The escaping for subtitle filters seems overzealous with the drive path. Unsure if it affects other filters, but I had no ill effects from my testing with my other FilterNodes. The output of the FilterNode class for .filter('subtitles','I:/file.mkv') results in subtitles=I\\\\\\:/file.mkv being passed to ffmpeg. However, this is causing one extra backslash to be passed to the program as seen in my errors. [Parsed_subtitles_1 @ 000001f0025056c0] Unable to open I\:/file.mkv I removed the last escape_chars call from this class and everything worked as expected. subtitles=I\\\:/file.mkv is being passed to ffmpeg with no errors.

For quick reference for any others facing this issue, here's a simple way to implement this solution:

import ffmpeg

### override filter code

def _new_get_filter(self, outgoing_edges):
    if self.name == 'subtitles' or self.name == 'ass':
        path = ffmpeg._utils.escape_chars(self.args[0], '\\\'=:')
        return f"{self.name}='{path}'"
    else:
        return self._orig_get_filter(outgoing_edges)

setattr(ffmpeg.nodes.FilterNode, '_orig_get_filter', ffmpeg.nodes.FilterNode.__dict__["_get_filter"])
setattr(ffmpeg.nodes.FilterNode, '_get_filter', _new_get_filter)

### now use ffmpeg as normal

input_path = 'C:\\test_video[0].mkv'
output_path = 'C:\\test_video_output.mkv'

ffmpeg.input(input_path).filter('subtitles', input_path).output(
    output_path, preset='veryfast', map='0:a').run(overwrite_output=True)

This method worked in the first place solving the path problem. However, adding it somehow won't recognize my force_style settings for the font anymore:

# subtitle
if use_subtitle == True:
    combined = combined.filter("subtitles", subtitle_file_processed, force_style="FontName=Microsoft YaHei,FontSize=13")

It will work if I append the escaped "force_style" part from "self._orig_get_filter(outgoing_edges)" to the return value f"{self.name}='{path}'". However, I don't know how to do it in the program

voidpenguin-28 commented 1 month ago

@zh7i

I think the following changes should fix it. Haven't tested it, so perhaps some adjustments may need to be made if any issues occur.

import ffmpeg

### override filter code

def _new_get_filter(self, outgoing_edges):
    if self.name in ('subtitles', 'ass'):
        s = '\\\'=:'
        e = ffmpeg._utils.escape_chars
        all_args = [e(a, s) for a in self.args]
        all_args.extend(['{}={}'.format(e(k, s), e(v, s)) for k, v in list(self.kwargs.items())])

        args_str = ':'.join(all_args)
        return f"{self.name}='{args_str}'"
    else:
        return self._orig_get_filter(outgoing_edges)

setattr(ffmpeg.nodes.FilterNode, '_orig_get_filter', ffmpeg.nodes.FilterNode.__dict__["_get_filter"])
setattr(ffmpeg.nodes.FilterNode, '_get_filter', _new_get_filter)

### now use ffmpeg as normal

input_path = 'C:\\test_video[0].mkv'
output_path = 'C:\\test_video_output.mkv'

ffmpeg.input(input_path).filter('subtitles', input_path).output(
    output_path, preset='veryfast', map='0:a').run(overwrite_output=True)
zh7i commented 1 month ago

@zh7i

I think the following changes should fix it. Haven't tested it, so perhaps some adjustments may need to be made if any issues occur.

import ffmpeg

### override filter code

def _new_get_filter(self, outgoing_edges):
    if self.name in ('subtitles', 'ass'):
        s = '\\\'=:'
        e = ffmpeg._utils.escape_chars
        all_args = [e(a, s) for a in self.args]
        all_args.extend(['{}={}'.format(e(k, s), e(v, s)) for k, v in list(self.kwargs.items())])

        args_str = ':'.join(all_args)
        return f"{self.name}='{args_str}'"
    else:
        return self._orig_get_filter(outgoing_edges)

setattr(ffmpeg.nodes.FilterNode, '_orig_get_filter', ffmpeg.nodes.FilterNode.__dict__["_get_filter"])
setattr(ffmpeg.nodes.FilterNode, '_get_filter', _new_get_filter)

### now use ffmpeg as normal

input_path = 'C:\\test_video[0].mkv'
output_path = 'C:\\test_video_output.mkv'

ffmpeg.input(input_path).filter('subtitles', input_path).output(
    output_path, preset='veryfast', map='0:a').run(overwrite_output=True)

@voidpenguin-28 I just verified it worked for me. Thanks a lot!