乐趣区

关于程序员:知乎文章转视频实现乞丐版本

前言

作者这个周末因为脚扭了所以没方法出去玩,只能在家宅着,看书之余发现了知乎的一个新性能,能够把文章转成视频,不须要本人编辑,十分傻瓜和无脑。

作为一个程序员,看到一个货色的第一眼就是想着怎么实现,正好本人也没法出门,所以就花了几个小时的工夫实现了一个乞丐版本的文字转视频。上面会介绍一下实现须要解决的几个问题和后续的优化方向。

实现文章转视频须要解决的几个问题

  • 文字宰割

    要生成视频,就必须有语音,为了实现朗诵的成果,就不能很机械的一个字一个字的读,也不能一段话间断的读上来没有进展。所以文字宰割的重点是可能依照人类朗诵的节奏将文字宰割。

  • 文字生成图片

    文字没有什么太麻烦的中央,只须要生成一张彩色的底图,而后将文字贴上去即可

  • 文字转换成语音

    文字转换成语音就不须要说了,须要留神的一点是不可能一字一句的读也不能一下子把全副的文字一股脑读出来,须要依照人类的朗诵形式发声

  • 图片 contact 成视频

    咱们晓得视频其实就是一张张图片的组成,咱们能够将没有声音的视频了解为一个正在播放的 PPT,所以在连贯图片的时候,须要思考到朗诵的速度,决定每一张图片须要展现多久的工夫

  • 语音 contact 成音频

    这个局部也是没有什么难点,只须要依照程序将语音连贯在一起即可

  • 音频和视频合并

    音频和视频合并也并不麻烦,只须要一条命令即可搞定

文字宰割

首先是文字宰割,市面上开源的分词工具都不满足依照语义宰割段落,例如结巴分词 (可能是我没找到,有理解的同学能够通知我)。所以为了放弃语义和语速,我应用了依照标点符号宰割文字的方法.

实现如下,行将标点符号之间的文字作为一句残缺的话,

text := ` 让座, 汽车在山路上平稳,刚上车的老大爷有些站不稳。车子满员,曾经无座,有几个多余的乘客站在廊道里忍受着游来荡去地平稳。穿黄色 T 恤衫的小伙子骂一句:“他妈的,刚修的水泥路没几年坏成这样, 都他妈只晓得偷工减料赚钱了”老大爷手紧紧抓着横杠身子游来荡去。天气有些闷热。老人家浑身散发出一股熏人的汗酸臭和泥涩味儿。`
var data []string
d := ""for _, str :=range strings.Split(text,"\n") {
   for _, t := range str {if !unicode.IsPunct(t) {d = fmt.Sprintf("%s%s", d, string(t))
      } else if unicode.IsSpace(t) { } else {
         if d != "" {data = append(data, d)
            d = ""
         }
      }
   }
}

文字生成图片

文字生成图片没有什么好说的,go 自身自带的包就足够了,不过须要留神的一点是,字体须要反对中文,否则会展现成乱码

func(i *Imager) genBaseImage(filename, text string) error {m := image.NewRGBA(image.Rect(0, 0, 1600, 900))
   draw.Draw(m, m.Bounds(), image.Black, image.ZP, draw.Src)
   
   if err := internal.CreateFolderIfNotExists("image"); err != nil {return err}

   // Read the font data.
   fontBytes, err := ioutil.ReadFile("./front/SourceHanSans-Bold.ttf")
   if err != nil {return err}
   f, err := freetype.ParseFont(fontBytes)
   if err != nil {return err}

   c := freetype.NewContext()
   c.SetDPI(72)
   c.SetFont(f)
   c.SetFontSize(defaultSize)
   c.SetClip(m.Bounds())
   c.SetDst(m)
   c.SetSrc(image.White)

   pt := freetype.Pt(350, 200+int(c.PointToFixed(defaultSize)>>6))
   if _, err := c.DrawString(text, pt); err != nil {return err}

   fd, err := os.Create(fmt.Sprintf("image/%s.jpeg", filename))
   if err != nil {return err}
   err = jpeg.Encode(fd, m, nil)
   if err != nil {return err}
   return nil
}

文字转换成语音

文字转换语音这一块我试了很读多工具 ffmpeg、especk、google 翻译,最终还是 google 翻译最为不便,ffmpeg 须要依照 filter,然而我始终装不上。especk 也是能够的,然而我没用过,不晓得成果怎么样,所以最终抉择了 google 翻译 的服务,只须要发一个申请即可。

func (speech *Speech) downloadIfNotExists(fileName string, text string) error {f, err := os.Open(fileName)
   if err != nil {response, err := http.Get(fmt.Sprintf("http://translate.google.com/translate_tts?ie=UTF-8&total=1&idx=0&textlen=32&client=tw-ob&q=%s&tl=%s", url.QueryEscape(text), speech.Language))
      if err != nil {return err}
      defer response.Body.Close()

      output, err := os.Create(fileName)
      if err != nil {return err}

      _, err = io.Copy(output, response.Body)
      return err
   }

   _ = f.Close()
   return nil
}

图片 contact 成视频

这个步骤会迎来一个第一个难点,图片生成之后,要如何连接成视频?每张图片要播放多久?这个中央我试了好几次,最终才确定字符数和语速的的关系,大略就是 8 个字符作为一秒是比较稳定的朗诵速度。

首先是如何连贯图片称为视频:

c1 := exec.Command("ffmpeg", "-f", "concat", "-i", "contact_image.txt", "-vsync", "vfr", "-pix_fmt", "yuv420p", "video.mp4")

生成配置文件告知 ffmpeg 依照什么样的播放速度合成视频:

for i, d := range data {fnImage := fmt.Sprintf("image0%d", i)
   fnAudio := fmt.Sprintf("audio0%d", i)
   s := Speech{
      Folder:   "audio",
      FileName: fnAudio,
      Language: "zh",
   }
   _ = s.GenerateAudio(d)
   img := Imager{}
   _ = img.genBaseImage(fnImage, d)

   fd_image.Write([]byte(fmt.Sprintf("file'image/%s.jpeg'\n", fnImage)))
   duration := 0
   if len(d)/ 3 > 1 {duration = len(d)/ 8
   }
   fd_image.Write([]byte(fmt.Sprintf("duration %d\n", duration)))

   fd_audio.Write([]byte(fmt.Sprintf("file'audio/%s.mp3'\n", fnAudio)))
}

语音 contact 成音频

语音连接成音频没有什么难点,一条命令即可搞定

c2 := exec.Command("ffmpeg", "-f", "concat", "-safe", "0", "-i", "contact_audio.txt", "-c", "copy", "output.mp3")

音频和视频合并

这一步也没有难度,只须要一条命令即可

c3 := exec.Command("ffmpeg", "-i", "video.mp4", "-i", "output.mp3", "-c:v", "copy", "-c:a", "aac", "output.mp4")

序幕

到此为止,文章转视频的实现就实现了,是不是很简略, 惟一的依赖,就是须要装置的 ffmpeg 了,置信这个不须要我通知你怎么装置了。

具体的代码能够参见我的 github, https://github.com/leoython/t…

TODO

在一开始我就说了,这其实是一个乞丐版本,还有很多能够优化的中央。

  • 文字的宰割,当初是依照标点符号来宰割的,如果能够应用机器学习训练的模型依照语义宰割,会更加像人类一些。
  • 文字转语音,当初的朗诵声还是很机械的,如果能够更加像人一些就好了
  • 文字转图片,代码中只实现了一个非常简单的版本,如果要工业化,起码须要判断文字的长度主动换行,主动判断字体大小。
退出移动版