程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 更多關於編程 >> bilibili彈幕轉ass法式制造思緒及進程

bilibili彈幕轉ass法式制造思緒及進程

編輯:更多關於編程

bilibili彈幕轉ass法式制造思緒及進程。本站提示廣大學習愛好者:(bilibili彈幕轉ass法式制造思緒及進程)文章只能為提供參考,不一定能成為您想要的結果。以下是bilibili彈幕轉ass法式制造思緒及進程正文


b站的彈幕,線下播放照樣挺費事的,公用的彈幕播放器對其他格局的視頻支撐欠好。我也試著弄個彈幕轉字幕的小法式出來。

抓取xml文件的任務就不多說了,很簡略的事,只需在播放頁面看看源文件就可以肯定xml文件的地址停止抓取了。

本文重要是講述xml內的彈幕轉字幕的進程。

除去xml文件開首開頭的一些七七八八的器械,彈幕主體是如許的:

<d p="51.593,5,25,16711680,1408852480,0,7fa769b4,576008622">怒求 up 本身配音!</d>
<d p="10.286,1,25,16777215,1408852600,0,a3af4d0d,576011065">顏藝?</d>
<d p="12.65,1,25,16777215,1408852761,0,24570b5a,576014281">我的女神!</d>
<d p="19.033,1,25,16777215,1408852789,0,cb20d1c7,576014847">前!!!</d>
<d p="66.991,1,25,16777215,1408852886,0,a78e484d,576016806">已撸</d>

假如它把彈幕的各類屬性離開表現,我就用encoding/xml包來解碼,然則丫把彈幕的屬性都放在p外面了,所以我應用正則表達式來提取的。

以上表第一條彈幕為例。很顯著的,p屬性開端的浮點數,與播放時一比對,就可以曉得,表現的是彈幕應當湧現的播放時光。

隨後的1和25先不論;

16777215,目測應當是色彩(由於該值表現為十六進制是FFFFFF);

1408852480,在彈幕中是遞增的,感到應當是個unix時光,用這個數(d),求:d/86400/365.2425+1970,成果約為2014.6。看來確切是unix時光。估量是創立彈幕的時光。

0,不曉得,抓取了許多視頻的彈幕,這個地位都是0,暫且不論它。

7fa769b4,估量是創立者的ID,由於統一xml文件會湧現屢次,並且看起來是十六進制數,正好有些hash函數就是前往4字節整數。

576008622,也是遞增的,不消猜也曉得,這個確定就是彈幕的ID了。

過後再查對一下,果真,1代表彈幕的類型(從右向左挪動啊,湧現鄙人方或許上方啊……),25是字體年夜小,16777125是字體色彩。

所以,我們就只需捕捉每條彈幕的時光、類型、年夜小、色彩、文本就好了。

正則表達式:

<d\sp="([\d\.]+),([145]),(\d+),(\d+),\d+,\d+,\w+,\d+">([^<>]+?)</d>


捕捉彈幕很簡略,症結是排布彈幕為字幕的算法。
關於這個算法我就很坑爹的弄了個雜亂無章的算法,采取的是固定挪動速度,最小堆疊的排布准繩。

對游動彈幕,會偏向於選擇上面一行的地位,假如會堆疊,則選擇更下一行(最低行會輪回到最下面一行),假如沒有不堆疊的行,會選擇堆疊文本起碼的行。

對上現隱/下現隱的固定彈幕,會選擇最接近上方/下方,且不堆疊的行;假如沒有不堆疊的行,則選擇堆疊時光最短的行,居中放置字幕。

默許字體微軟雅黑,默許年夜小25,默許白色黑邊;默許占滿全部屏幕,合計12行;默許屏幕年夜小640x360。

這麼弄,重要是為了讓ass字幕的後果更接近原始彈幕的後果。

高等彈幕真的超越我的才能規模了,全體疏忽失落。

go源代碼以下:

// 將bilibili的xml彈幕文件轉換為ass字幕文件。
// xml文件中,彈幕的格局以下:
// <d p="32.066,1,25,16777215,1409046965,0,017d3f58,579516441">地板好評</d>
// p的屬性為時光、彈幕類型、字體年夜小、字體色彩、創立時光、?、創立者ID、彈幕ID。
// p的屬性中,後4項對ass字幕無用,捨棄。被<d>和</d>包抄的是彈幕文本。
// 只處置右往左、上現隱、下現隱三品種型的通俗彈幕。
package main
 
import (
  "fmt"
  "io"
  "io/ioutil"
  "math"
  "os"
  "regexp"
  "sort"
  "strconv"
  "strings"
)
 
// ass文件的頭部
const header = `[Script Info]
ScriptType: v4.00+
Collisions: Normal
playResX: 640
playResY: 360
 
[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, Microsoft YaHei, 28, &H00FFFFFF, &H00FFFFFF, &H00000000, &H00000000, 0, 0, 0, 0, 100, 100, 0.00, 0.00, 1, 1, 0, 2, 10, 10, 10, 0
 
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
`
 
// 正則婚配獲得彈幕原始信息
var line = regexp.MustCompile(`<d\sp="([\d\.]+),([145]),(\d+),(\d+),\d+,\d+,\w+,\d+">([^<>]+?)</d>`)
 
// 用來保管彈幕的信息
type Danmu struct {
  text string
  time float64
  kind byte
  size int
  color int
}
 
// 使[]Danmu完成sort.Interface接口,以便排序
type Danmus []Danmu
 
func (d Danmus) Len() int {
  return len(d)
}
func (d Danmus) Less(i, j int) bool {
  return d[i].time < d[j].time
}
func (d Danmus) Swap(i, j int) {
  d[i], d[j] = d[j], d[i]
}
 
// 將正則婚配到的數據填寫入Danmu類型裡
func fill(d *Danmu, s [][]byte) {
  d.time, _ = strconv.ParseFloat(string(s[1]), 64)
  d.kind = s[2][0] - '0'
  d.size, _ = strconv.Atoi(string(s[3]))
  bgr, _ := strconv.Atoi(string(s[4]))
  d.color = ((bgr >> 16) & 255) | (bgr & (255 << 8)) | ((bgr & 255) << 16)
  d.text = string(s[5])
}
 
// 前往文本的長度,假定ascii字符都是0.5個字長,其他都是1個字長
func length(s string) float64 {
  l := 0.0
  for _, r := range s {
    if r < 127 {
      l += 0.5
    } else {
      l += 1
    }
  }
  return l
}
 
// 生成時光點的ass格局表現:`0:00:00.00`
func timespot(f float64) string {
  h, f := math.Modf(f / 3600)
  m, f := math.Modf(f * 60)
  return fmt.Sprintf("%d:%02d:%05.2f", int(h), int(m), f*60)
}
 
// 讀取文件並獲得個中的彈幕
func open(name string) ([]Danmu, error) {
  data, err := ioutil.ReadFile(name)
  if err != nil {
    return nil, err
  }
  dan := line.FindAllSubmatch(data, -1)
  ans := make([]Danmu, len(dan))
  for i := len(dan) - 1; i >= 0; i-- {
    fill(&ans[i], dan[i])
  }
  return ans, nil
}
 
// 將彈幕排布並寫入w,采取的簡略的固定移速、最小堆疊排布算法
func save(w io.Writer, dans []Danmu) {
  p1 := make([]float64, 36)
  p2 := make([]float64, 36)
  p3 := make([]float64, 36)
  t := 0
  max := func(x []float64) float64 {
    i := x[0]
    for _, j := range x[1:] {
      if i < j {
        i = j
      }
    }
    return i
  }
  set := func(x []float64, f float64) {
    for i, _ := range x {
      x[i] = f
    }
  }
  find := func(p []float64, f float64, i, d int) int {
    i = (i/d + 1) * d % 36
    m, k := f+10000, 0
    for j := 0; j < 36; j += d {
      t := (i + j) % 36
      if n := max(p[t : t+d]); n <= f {
        k = t
        break
      } else if m > n {
        k = t
        m = n
      }
    }
    return k
  }
  for _, dan := range dans {
    s, l := "", length(dan.text)
    if l == 0 {
      continue
    }
    switch {
    case dan.size < 25:
      dan.size, l, s = 2, l*18, "\\fs18"
    case dan.size == 25:
      dan.size, l = 3, l*28
    case dan.size > 25:
      dan.size, l, s = 4, l*38, "\\fs38"
    }
    if dan.color != 0x00FFFFFF {
      s += fmt.Sprintf("\\c&H%06X", dan.color)
    }
    switch dan.kind {
    case 1: // 右往左
      t := find(p1, dan.time, t, dan.size)
      set(p1[t:t+dan.size], dan.time+8)
      h := (t+dan.size)*10 - 1
      s += fmt.Sprintf("\\move(%d,%d,%d,%d)", 640+int(l/2), h, -int(l/2), h)
      fmt.Fprintf(w, "Dialogue: 1,%s,%s,Default,,0000,0000,0000,,{%s}%s\n",
        timespot(dan.time+0),
        timespot(dan.time+8), s, dan.text)
    case 4: // 下現隱
      j := find(p2, dan.time, 35, dan.size)
      set(p2[j:j+dan.size], dan.time+4)
      s += fmt.Sprintf("\\pos(%d,%d)", 320, (36-j)*10-1)
      fmt.Fprintf(w, "Dialogue: 2,%s,%s,Default,,0000,0000,0000,,{%s}%s\n",
        timespot(dan.time+0),
        timespot(dan.time+4), s, dan.text)
    case 5: // 上現隱
      j := find(p3, dan.time, 35, dan.size)
      set(p3[j:j+dan.size], dan.time+4)
      s += fmt.Sprintf("\\pos(%d,%d)", 320, (j+dan.size)*10-1)
      fmt.Fprintf(w, "Dialogue: 3,%s,%s,Default,,0000,0000,0000,,{%s}%s\n",
        timespot(dan.time+0),
        timespot(dan.time+4), s, dan.text)
    }
  }
}
 
// 主函數,完成了敕令行
func main() {
  if len(os.Args) <= 1 {
    os.Exit(0)
  }
  for _, name := range os.Args[1:] {
    dans, err := open(name)
    if err != nil {
      os.Exit(1)
    }
    if n := strings.LastIndex(name, "."); n != -1 {
      name = name[:n]
    }
    name += ".ass"
    file, err := os.Create(name)
    if err != nil {
      os.Exit(2)
    }
    file.WriteString(header)
    sort.Sort(Danmus(dans))
    save(file, dans)
    file.Close()
  }
}


2014.9.2 9:30am更新:對字體排布停止了修改。

2014.9.2 9:50am更新:算法修正為固定湧現時光,最小堆疊排布,終究版本。

over。迎接列位評論,倒不如列位多多評論啊。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved