Answers.Body: <p>I know this question is about achieving the result with the <code>drawtext</code> filter.</p>
  
  <p>However this might be an alternative approach using the <code>ass</code> filter.</p>
  
  <pre><code>in="Slow_when_flashing_school_zone_traffic_sign_in_Chinese.jpg"
  txt="school_sign"
  
  ffmpeg -i "$in" \
  -vf "zoompan=z='min(zoom+0.005,1.5)':y='if(gte(zoom,1.5),y,y+1)':x='x':d=125,\
  scale=w=600:h=800,\
  fps=fps=25,\
  ass=${txt}.ass" -f nut pipe: | ffplay pipe:
  </code></pre>
  
  <p>Preview: (the color changing text is the animated subtitle)
  <a href="https://i.sstatic.net/3UIMF.gif" rel="nofollow noreferrer"><img src="https://i.sstatic.net/3UIMF.gif" alt="Preview of animated ASS subtitle"></a></p>
  
  <p>Since ASS is very flexible and can be formatted extensively this might be worthwhile to explore.</p>
  
  <p>You might want to have a look at <a href="http://docs.aegisub.org/3.2/Visual_Typesetting/" rel="nofollow noreferrer">Visual_Typesetting</a> and <a href="http://docs.aegisub.org/3.2/ASS_Tags/" rel="nofollow noreferrer">ASS Override Tags</a> (search for <code>Animated transform</code> and <code>\t tag</code>).</p>
  
  <p>I suggest to generate the ASS file with <a href="http://www.aegisub.org/downloads/" rel="nofollow noreferrer">Aegisub</a> as a starting point. If you need to, you could further improve it with a text editor.</p>
  
  <p>File and Image:</p>
  
  <p>File: <code>school_sign.ass</code> (proof of concept)</p>
  
  <pre><code>[Script Info]
  ; Script generated by Aegisub 3.2.2
  ; http://www.aegisub.org/
  Title: Default Aegisub file
  ScriptType: v4.00+
  WrapStyle: 0
  ScaledBorderAndShadow: yes
  YCbCr Matrix: PC.601
  PlayResX: 600
  PlayResY: 800
  
  [Aegisub Project Garbage]
  Video AR Mode: 4
  Video AR Value: 0.750000
  Video Zoom Percent: 2.000000
  
  [V4+ Styles]
  Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
  Style: Default,Arial,20,&amp;H00FFFFFF,&amp;H000000FF,&amp;H00000000,&amp;H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1
  
  [Events]
  Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
  Dialogue: 0,0:00:00.00,0:00:04.94,Default,,0,0,0,,{\an5\fscx100\fscy100\t(0,3960,\fscx150\fscy150)\frz5.811\4c&amp;H000000&amp;\4a&amp;HFF&amp;\c&amp;H2A2A2A&amp;\3c&amp;H000000&amp;\3a&amp;HFF&amp;\move(307,527,460,755,0,3920)}Slow when flashing! School zone
  </code></pre>
  
  <p>Image: <code>Slow_when_flashing_school_zone_traffic_sign_in_Chinese.jpg</code></p>
  
  <p>Daniel Case / CC BY-SA (<a href="https://creativecommons.org/licenses/by-sa/3.0" rel="nofollow noreferrer">https://creativecommons.org/licenses/by-sa/3.0</a>)
  <a href="https://i.sstatic.net/K0SQN.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/K0SQN.jpg" alt="Slow_when_flashing_school_zone_traffic_sign_in_Chinese.jpg"></a></p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-02-29T12:27:43.853Z
Answers.LastActivityDate: 2020-02-29T12:55:10.267Z
Answers.LastEditDate: 2020-02-29T12:55:10.267Z
Answers.UserDisplayName: drake7
Answers.Body: <p>I call FFMPEG from python and this worked with me:</p>
  
  <pre><code>from PIL import ImageFont
  
  translation_font = ImageFont.truetype(
      "SourceCodePro-Regular.otf", size=55, encoding="unic")
  w, _ = translation_font.getsize(" ".join([line, word]))
  </code></pre>
  
  <p>Which will put the width of the text (rendered with the given settings) in <code>w</code>. </p>
  
  <p>Based on that you can find the first word that makes the text get outside of the screen and insert a new line before it</p>
  
  <pre><code>def fit_text(string: str, frame_width):
      split_line = [x.strip() for x in string.split()]
      lines = ""
      w = 0
      line_num = 0
      line = ""
      for word in split_line:
          # Make a test
          w, _ = translation_font.getsize(" ".join([line, word]))
          # If it exceeds the frame width, add a new line
          if w &gt; (frame_width - (2 * 6)):  # Leave 6px margin on each side
              lines += line.strip() + "\n"
              line = ""
  
          line += word + " "
  
      lines += line.strip()  # Append leftover words
      return lines
  </code></pre>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2019-04-27T13:07:44.227Z
Answers.LastActivityDate: 2019-04-27T13:07:44.227Z
Answers.UserDisplayName: shakram02
Answers.Body: <p>My solution that works well was to manually measure every letter width. I typed letters in Photoshop at a relatvely large font size to minimize rounding effect of pixel size and measured widths in pixels.</p>
  
  <p>Given the word I determine its length and calculate the scale factor to make it fit required length.</p>
  
  <p>Function that calculates the word width for the Cyrilllic alphabet and a paricular typeface:</p>
  
  <pre><code># Given string calculate its length based on the font metrics.
  # Letter widhts measured in pixels at 100pt font size in Photoshop.
  function textWidth {
      local word=$1
      local varname=$2
  
      local widths=(73 59 65 52 80 56 56 105 66 73 73 75 76 102 69 78 70 62 67 64 74 86 76 81 66 90 102 75 84 63 60 105 70)
      local alpha="абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
      local total=0
      local i
  
      strindex() { 
          local x="${1%%$2*}"
          local length=${widths["${#x}"]}
          total=$(( length &gt; 0 ? total + length : total + 74 )) 
      }
  
      for i in $(seq 1 ${#word}); do
          strindex $alpha ${word:i-1:1}
      done
  
      eval $varname="\"$total\""
  }
  </code></pre>
  
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2018-03-27T07:07:27.273Z
Answers.Text: Hey, @Serge thanks a ton. I will update you how it goes. Have a good day ahead.
Answers.UserDisplayName: Raju
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2018-03-27T07:21:16.847Z
Answers.Text: I would need it for English alphabets and few supported special symbols, I will have to recalculate the width I suppose. Also, I may have to extend this feature to a different language in future.
Answers.UserDisplayName: Raju
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2018-03-27T06:14:00.313Z
Answers.LastActivityDate: 2018-03-27T06:14:00.313Z
Answers.UserDisplayName: Serge
Answers.Body: <p>There's no way to force the drawtext filter to fit the text in a certain width.</p>
  
  <p>What you can do, is get the rendered text dimensions in a first pass and then run drawtext accordingly in 2nd pass, or crop and scale the output using the values from first pass.</p>
  
  <p>For first pass,</p>
  
  <pre><code>ffmpeg -i video -vf drawtext=fontfile=path/to/font:fontsize=FinalSize:text='Lorem Ipsor' \
                     :x=0+0*print(tw):y=0+0*print(th) -vframes 1 -f null -
  </code></pre>
  
  <p>This will print the text at the top-left. The print statements will output</p>
  
  <pre><code>84.000000
  14.000000
  </code></pre>
  
  <p>The first value is <code>tw</code> - the width, and the 2nd <code>th</code> - the height, in pixels.</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-08-24T18:16:48.803Z
Answers.Text: I love this idea, but that print(tw) just gives me an unexpected token error from ffmpeg.
Answers.UserDisplayName: ezwrighter
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-08-24T19:11:16.423Z
Answers.Text: Usually a matter of correctly quoting and/or escaping arguments, based on your shell.
Answers.UserDisplayName: Gyan
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-08-24T22:13:03.837Z
Answers.Text: Thanks....yes, Bash was barfing on the parens without quoting the filter.
Answers.UserDisplayName: ezwrighter
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-08-28T18:06:53.207Z
Answers.Text: Also, I ran into an issue of way to much output to stderr for grabbing those values, so I used print(tw,0) which put the results in the "panic" loglevel, making it easy to set "-loglevel panic" and only have the width/height returned.  However it wanted to do this for the entire video which ended in buffer overflow, so I also set "-t 1" before the input video to limit the input length to 1 second.
Answers.UserDisplayName: ezwrighter
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-01-25T13:22:28.960Z
Answers.Text: Better than using `print`, is using `-loglevel debug`, you'll get something like `[Parsed_drawtext_0 @ 0x7fc1c3704c00] n:0 t:0.000000 text_w:138 text_h:33 x:0 y:0`
Answers.UserDisplayName: unloco
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2022-01-25T16:45:58.247Z
Answers.Text: `debug` will produce a lot of text. You'll then to need to use grep or similar to pare it down.
Answers.UserDisplayName: Gyan
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2017-10-05T10:08:47.380Z
Answers.LastActivityDate: 2017-10-05T10:08:47.380Z
Answers.UserDisplayName: Gyan
Body: <p>A script takes a single word 3 to 11 characters long, draws it with a custom font and overlays on top of a video. The word should fit a certain width.</p>
  
  <p>The animation involves pan and zoom that the word follows. Maybe this compositing job suits smth like After Effects, but the task is ffmpeg implementation. I made it work with the shortest 3-character long sample word and the longest 11-character one.</p>
  
  <p><code>drawtext</code> filter generates a transparent png with big margins around the text. <code>zoompan</code> filter does the zooming afterwards.</p>
  
  <p>I determined the font size for 3- and 11-char sample words and tried linear interpolation to get font size, depending on the word length. This approach fails with some words, as the font has variable width letters, and "MMM" vs. "III" differs n times in width.</p>
  
  <p>Is there a way in ffmpeg to fit a variable-width text to fixed dimensions?</p>
  
  <p>Maybe some filter to detect a least-sized rectangle with non-transparent pixels? </p>
  
  <p><strong>Plan-B</strong> works fine: bash function that calculates word length depending on each letter width in the particular font. Had to measure each letter width in Photoshop in pixels at 100pt font size.</p>
  
  <p>Still wondering if there is a way <em>ffmpeg</em> could scale font size text to fit some fixed width.</p>
  
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2018-03-26T18:20:12.977Z
Comments.Text: Were you able to find any FFmpeg command option to solve this problem? 
  
  Is bash function mentioned in Plan-B working for you? Can you share that bash function here? I am also facing the similar issue and not able to find a solution yet.
Comments.UserDisplayName: Raju
Comments.ContentLicense: CC BY-SA 3.0
Comments.CreationDate: 2018-03-27T06:14:41.120Z
Comments.Text: @Raju I added my [answer](https://video.stackexchange.com/a/23660/20162) with the solution and the code.
Comments.UserDisplayName: Serge
Tags: <ffmpeg>
Title: How to fit word to width with ffmpeg drawtext filter?
UserDisplayName: Serge
ThreadText: # How to fit word to width with ffmpeg drawtext filter?
  
  (Asked by: username_0 on 2017-10-04)
  
  A script takes a single word 3 to 11 characters long, draws it with a custom font and overlays on top of a video. The word should fit a certain width.
  
  The animation involves pan and zoom that the word follows. Maybe this compositing job suits smth like After Effects, but the task is ffmpeg implementation. I made it work with the shortest 3-character long sample word and the longest 11-character one.
  
  `drawtext` filter generates a transparent png with big margins around the text. `zoompan` filter does the zooming afterwards.
  
  I determined the font size for 3- and 11-char sample words and tried linear interpolation to get font size, depending on the word length. This approach fails with some words, as the font has variable width letters, and "MMM" vs. "III" differs n times in width.
  
  Is there a way in ffmpeg to fit a variable-width text to fixed dimensions?
  
  Maybe some filter to detect a least-sized rectangle with non-transparent pixels?
  
  **Plan-B** works fine: bash function that calculates word length depending on each letter width in the particular font. Had to measure each letter width in Photoshop in pixels at 100pt font size.
  
  Still wondering if there is a way *ffmpeg* could scale font size text to fit some fixed width.
  
  **Tags:** ffmpeg
  
  > **Comments:**
  > **username_1**: Were you able to find any FFmpeg command option to solve this problem?   Is bash function mentioned in Plan-B working for you? Can you share that bash function here? I am also facing the similar issue and not able to find a solution yet.  
  > **username_0**: @username_1 I added my [answer](https://video.stackexchange.com/a/23660/20162) with the solution and the code.  
  
  ---
  
  ## 4 answers
  
  ### Answer by username_4 on 2017-10-05. Score: 7 | Accepted answer
  
  There's no way to force the drawtext filter to fit the text in a certain width.
  
  What you can do, is get the rendered text dimensions in a first pass and then run drawtext accordingly in 2nd pass, or crop and scale the output using the values from first pass.
  
  For first pass,
  
  ```
  ffmpeg -i video -vf drawtext=fontfile=path/to/font:fontsize=FinalSize:text='Lorem Ipsor' \
                     :x=0+0*print(tw):y=0+0*print(th) -vframes 1 -f null -
  
  ```
  
  This will print the text at the top-left. The print statements will output
  
  ```
  84.000000
  14.000000
  
  ```
  
  The first value is `tw` - the width, and the 2nd `th` - the height, in pixels.
  
  > **Comments:**
  > **username_5**: I love this idea, but that print(tw) just gives me an unexpected token error from ffmpeg.  
  > **username_4**: Usually a matter of correctly quoting and/or escaping arguments, based on your shell.  
  > **username_5**: Thanks....yes, Bash was barfing on the parens without quoting the filter.  
  > **username_5**: Also, I ran into an issue of way to much output to stderr for grabbing those values, so I used print(tw,0) which put the results in the "panic" loglevel, making it easy to set "-loglevel panic" and only have the width/height returned.  However it wanted to do this for the entire video which ended in buffer overflow, so I also set "-t 1" before the input video to limit the input length to 1 second.  
  > **username_6**: Better than using `print`, is using `-loglevel debug`, you'll get something like `[Parsed_drawtext_0 @ 0x7fc1c3704c00] n:0 t:0.000000 text_w:138 text_h:33 x:0 y:0`  
  > **username_4**: `debug` will produce a lot of text. You'll then to need to use grep or similar to pare it down.  
  
  ---
  
  ### Answer by username_2 on 2020-02-29. Score: 3
  
  I know this question is about achieving the result with the `drawtext` filter.
  
  However this might be an alternative approach using the `ass` filter.
  
  ```
  in="Slow_when_flashing_school_zone_traffic_sign_in_Chinese.jpg"
  txt="school_sign"
  
  ffmpeg -i "$in" \
  -vf "zoompan=z='min(zoom+0.005,1.5)':y='if(gte(zoom,1.5),y,y+1)':x='x':d=125,\
  scale=w=600:h=800,\
  fps=fps=25,\
  ass=${txt}.ass" -f nut pipe: | ffplay pipe:
  
  ```
  
  Preview: (the color changing text is the animated subtitle)
  [![Preview of animated ASS subtitle](https://i.sstatic.net/3UIMF.gif)](https://i.sstatic.net/3UIMF.gif)
  
  Since ASS is very flexible and can be formatted extensively this might be worthwhile to explore.
  
  You might want to have a look at [Visual\_Typesetting](http://docs.aegisub.org/3.2/Visual_Typesetting/) and [ASS Override Tags](http://docs.aegisub.org/3.2/ASS_Tags/) (search for `Animated transform` and `\t tag`).
  
  I suggest to generate the ASS file with [Aegisub](http://www.aegisub.org/downloads/) as a starting point. If you need to, you could further improve it with a text editor.
  
  File and Image:
  
  File: `school_sign.ass` (proof of concept)
  
  ```
  [Script Info]
  ; Script generated by Aegisub 3.2.2
  ; http://www.aegisub.org/
  Title: Default Aegisub file
  ScriptType: v4.00+
  WrapStyle: 0
  ScaledBorderAndShadow: yes
  YCbCr Matrix: PC.601
  PlayResX: 600
  PlayResY: 800
  
  [Aegisub Project Garbage]
  Video AR Mode: 4
  Video AR Value: 0.750000
  Video Zoom Percent: 2.000000
  
  [V4+ Styles]
  Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
  Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1
  
  [Events]
  Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
  Dialogue: 0,0:00:00.00,0:00:04.94,Default,,0,0,0,,{\an5\fscx100\fscy100\t(0,3960,\fscx150\fscy150)\frz5.811\4c&H000000&\4a&HFF&\c&H2A2A2A&\3c&H000000&\3a&HFF&\move(307,527,460,755,0,3920)}Slow when flashing! School zone
  
  ```
  
  Image: `Slow_when_flashing_school_zone_traffic_sign_in_Chinese.jpg`
  
  Daniel Case / CC BY-SA (<https://creativecommons.org/licenses/by-sa/3.0>)
  [![Slow_when_flashing_school_zone_traffic_sign_in_Chinese.jpg](https://i.sstatic.net/K0SQN.jpg)](https://i.sstatic.net/K0SQN.jpg)
  
  
  ---
  
  ### Answer by username_3 on 2019-04-27. Score: 1
  
  I call FFMPEG from python and this worked with me:
  
  ```
  from PIL import ImageFont
  
  translation_font = ImageFont.truetype(
      "SourceCodePro-Regular.otf", size=55, encoding="unic")
  w, _ = translation_font.getsize(" ".join([line, word]))
  
  ```
  
  Which will put the width of the text (rendered with the given settings) in `w`.
  
  Based on that you can find the first word that makes the text get outside of the screen and insert a new line before it
  
  ```
  def fit_text(string: str, frame_width):
      split_line = [x.strip() for x in string.split()]
      lines = ""
      w = 0
      line_num = 0
      line = ""
      for word in split_line:
          # Make a test
          w, _ = translation_font.getsize(" ".join([line, word]))
          # If it exceeds the frame width, add a new line
          if w > (frame_width - (2 * 6)):  # Leave 6px margin on each side
              lines += line.strip() + "\n"
              line = ""
  
          line += word + " "
  
      lines += line.strip()  # Append leftover words
      return lines
  
  ```
  
  
  ---
  
  ### Answer by username_0 on 2018-03-27. Score: 0
  
  My solution that works well was to manually measure every letter width. I typed letters in Photoshop at a relatvely large font size to minimize rounding effect of pixel size and measured widths in pixels.
  
  Given the word I determine its length and calculate the scale factor to make it fit required length.
  
  Function that calculates the word width for the Cyrilllic alphabet and a paricular typeface:
  
  ```
  # Given string calculate its length based on the font metrics.
  # Letter widhts measured in pixels at 100pt font size in Photoshop.
  function textWidth {
      local word=$1
      local varname=$2
  
      local widths=(73 59 65 52 80 56 56 105 66 73 73 75 76 102 69 78 70 62 67 64 74 86 76 81 66 90 102 75 84 63 60 105 70)
      local alpha="абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
      local total=0
      local i
  
      strindex() { 
          local x="${1%%$2*}"
          local length=${widths["${#x}"]}
          total=$(( length > 0 ? total + length : total + 74 )) 
      }
  
      for i in $(seq 1 ${#word}); do
          strindex $alpha ${word:i-1:1}
      done
  
      eval $varname="\"$total\""
  }
  
  ```
  
  > **Comments:**
  > **username_1**: Hey, @username_0 thanks a ton. I will update you how it goes. Have a good day ahead.  
  > **username_1**: I would need it for English alphabets and few supported special symbols, I will have to recalculate the width I suppose. Also, I may have to extend this feature to a different language in future.  
  
  ---
  
  