🎬 第六章 FFmpeg命令行实现图片水印及文字跑马灯
📄 一句话概括

fontfile 与 movie 走同一套解析器,规则完全一样:换斜杠、转冒号。 引号是 shell 的事,不是滤镜的事——这两件事经常被混为一谈。

📝 本节和前面几节的关系

本章最后一节,把路径这条线收口。 6.2.2 讲的是 movie,这一节是 fontfile,同一个坑换个位置再来一次。

一、四种写法,结论与 movie 完全一致

写法 字符串开头 结果


原样 I:\Desktop\… 失败 只换斜杠 I:/Desktop/… 失败 只转义冒号 I\:\Desktop\… 失败 斜杠加转义 I\:/Desktop/… 成功

四种组合的结果与 6.2.2 一模一样,一字节都不差。 因为 fontfile 与 movie 的参数都走同一套解析:冒号分隔、正斜杠认路。

学会一次,这一课里所有带路径的参数都能用: movie、fontfile、textfile、subtitles,规则统一。

二、带空格的路径:引号该不该加

写法 结果 为什么


直接给带空格的路径 成功 接口层不看空格 再套一层单引号 失败 引号成了文件名的一部分

第一行成功,加了引号的第二行反而失败——这一点最容易想反。 因为单引号是 shell 的语法,不是滤镜的语法。

命令行里是 shell 先剥掉引号、再把干净的字符串交给 FFmpeg; 而代码里直接调接口,中间没有 shell,加引号就等于文件名里多了两个字符。

所以「要不要套引号」取决于你在哪一层写字,不是滤镜的硬性规定。 这一句话能解释掉一大半「同样的命令,代码里就是不行」的困惑。

三、四条规则,收个尾

规则 为什么


反斜杠换正斜杠 滤镜内部分隔符只认正斜杠 冒号前加反斜杠 冒号是参数分隔符 命令行里整体套引号 避免 shell 按空格拆参数 用绝对路径 避免工作目录不同找不到

四条一起做,路径问题就基本绝迹了。 最难记的是第二条——它只发生在 Windows 上,Linux 路径里没有冒号。

所以网上绝大多数教程压根不会提这件事, 而你会在自己的机器上一次次撞上它。

写成一个函数,以后所有路径都过一遍它,就不再需要记了: p.replace("\\", "/").replace(":", "\\:"),两行,够用一辈子。

四、动手 Demo:四种写法与带空格的路径

先把同一个文件按四种方式拼路径,逐个交给 movie 验证。 再拿一个文件名带空格的副本,试「加引号」与「不加引号」。

import av, os, shutil

def esc(p):
    return p.replace(u"\\", u"/").replace(u":", u"\\:")

src = os.path.abspath(u"s5.mp4")
for tag, p in ((u"原样", src),
                (u"只换斜杠", src.replace(u"\\", u"/")),
                (u"只转义冒号", src.replace(u":", u"\\:")),
                (u"斜杠加转义", esc(src))):
    try:
        g = av.filter.Graph()
        m = g.add(u"movie", p)
        s = g.add(u"buffersink")
        m.link_to(s)
        g.configure()
        s.pull()
        print(u"%-12s 成功" % tag)
    except Exception:
        print(u"%-12s 失败" % tag)

sp = os.path.join(os.path.dirname(src), u"a b.mp4")
if not os.path.isfile(sp):
    shutil.copyfile(src, sp)
for tag, p in ((u"不加引号", esc(sp)), (u"加单引号", u"'" + esc(sp) + u"'")):
    try:
        g = av.filter.Graph()
        m = g.add(u"movie", p)
        s = g.add(u"buffersink")
        m.link_to(s)
        g.configure()
        s.pull()
        print(u"%-10s 成功" % tag)
    except Exception:
        print(u"%-10s 失败" % tag)

实跑输出:

--------------------------------------------------------------------
6.6.3 drawtext 中使用绝对路径——与 movie 同一条规则
--------------------------------------------------------------------

**fontfile 与 movie 走的是同一套参数解析器,所以规则完全一样。**
**拿 movie 读一个带空格的文件,把这条规则验一遍。**

**① 四种写法**

  写法           字符串开头                      结果
  -------------------------------------------------------
  原样           I:\Desktop\自我教学\.workb…     失败
  只换斜杠       I:/Desktop/自我教学/.workb…     失败
  只转义冒号     I\:\Desktop\自我教学\.work…     失败
  斜杠加转义     I\:/Desktop/自我教学/.work…     成功

**② 路径里有空格会怎样**

  写法                   结果     为什么
  ----------------------------------------------------------
  直接给带空格的路径     成功     接口层不看空格
  再套一层单引号         失败     引号成了文件名的一部分

**第一行成功,加了引号的第二行反而失败——这一点最容易想反。**
**因为单引号是 shell 的语法,不是滤镜的语法。**
**命令行里是 shell 先把引号剥掉、再把干净的字符串交给 FFmpeg;**
**而这段代码直接调接口,中间没有 shell,加引号就等于在文件名里多了两个字符。**
**所以「要不要套引号」取决于你在哪一层写字,不是滤镜的硬性规定。**

**③ 一张对照表,四条规则**

  规则                   为什么
  -----------------------------------------------------
  反斜杠换正斜杠         滤镜内部分隔符只认正斜杠
  冒号前加反斜杠         冒号是参数分隔符
  命令行里整体套引号     避免 shell 按空格拆参数
  用绝对路径             避免工作目录不同导致找不到

**四条一起做,路径问题就基本绝迹了。**
**最难记的是第二条——它只发生在 Windows 上,Linux 的路径里没有冒号,**
**所以网上绝大多数教程压根不会提这件事。**

os.path.abspath(u"s5.mp4") 拿到的是 Windows 风格路径,反斜杠加盘符冒号。 这正是最需要转义的那种字符串,拿它做实验最贴近真实场景。

加引号那一行是把 ' 当成字符串的一部分拼进去的, 所以它拼出来的文件名里真的有两个引号字符——当然找不到文件。

在 PowerShell 或 cmd 里,这两行的结果会反过来:不加引号才失败。 差别不在滤镜,而在「谁先解析」——这就是这一节最值得带走的一句话。

30 秒实验

  1. 换目录:把文件挪到 C:\Program Files\x\,看空格与括号要不要额外处理。
  2. 换参数:把 movie 换成 textfile=...,验证同一套转义规则。
  3. 封装函数:把 esc 写进自己的工具库,以后所有路径都过一遍。

常见误区

  1. 只做一半转义。只换斜杠或只转冒号,都还是失败。
  2. 在代码里加单引号。引号会变成文件名的一部分,找不到文件。
  3. 以为命令行与代码行为一致。中间差了一个 shell,行为就可能相反。
自测三问(点开对答案) 1. fontfile 与 movie 的路径规则为什么一样? —— 它们都是滤镜参数,走同一套解析:冒号分隔、正斜杠认路。 2. 为什么在代码里加单引号会失败? —— 没有 shell 去剥引号,引号成了文件名的字符。 3. 两行转义函数怎么写? —— 先把反斜杠换成正斜杠,再把冒号换成「反斜杠加冒号」。

相关笔记