麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學院 > 編程設計 > 正文

bilibili彈幕轉ass程序制作思路及過程

2020-02-02 19:01:04
字體:
來源:轉載
供稿:網友

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: NormalplayResX: 640playResY: 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, EncodingStyle: 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, "http://fs18"    case dan.size == 25:      dan.size, l = 3, l*28    case dan.size > 25:      dan.size, l, s = 4, l*38, "http://fs38"    }    if dan.color != 0x00FFFFFF {      s += fmt.Sprintf("http://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("http://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("http://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("http://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。歡迎各位評論,倒不如各位多多評論啊。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 黄色网欧美 | 久久区二区 | 亚欧在线免费观看 | 毛片在线免费播放 | 91精品免费在线 | 国产a级久久 | 日本xxxx色视频在线观看免费, | 18pao国产成人免费视频 | 一边吃奶一边摸下娇喘 | 免费a级毛片大学生免费观看 | 午色影院 | 久久国产秒 | 国产精品一区在线看 | 91伊人久久| 禁漫天堂久久久久久久久久 | 色播一区 | 黄色a级片免费观看 | 欧美a区| 性欧美大战久久久久久久免费观看 | 国产午夜精品一区 | 老子午夜影院 | 久久精品综合视频 | 国产91免费看| 中文字幕一区久久 | 成人短视频在线观看 | 99精品视频在线导航 | 亚洲精品午夜视频 | 永久免费不卡在线观看黄网站 | 97人人草| 精品一区在线视频 | 暴力强行进如hdxxx | 色污视频在线观看 | 国产午夜精品在线 | 日韩欧美精品中文字幕 | 免费黄色在线电影 | 国产精品自在线拍 | 亚洲免费观看视频 | 久久久久.com | 爱射av | 国产精品视频免费在线观看 | wankzhd|