亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

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

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。歡迎各位評論,倒不如各位多多評論啊。

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

圖片精選

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
影音先锋欧美在线资源| 91精品国产自产在线老师啪| 精品久久国产精品| 欧美激情视频在线免费观看 欧美视频免费一| 97视频免费在线观看| 久久久免费观看| 97国产成人精品视频| 亚洲三级黄色在线观看| 国产成人精品a视频一区www| 国产91精品高潮白浆喷水| 国产成人精品在线视频| 亚洲国产精品久久精品怡红院| 亚洲性生活视频在线观看| 主播福利视频一区| 日韩av在线资源| 91中文字幕一区| 正在播放欧美视频| 日韩av在线直播| 亚洲3p在线观看| 亚洲电影免费观看高清完整版| 成人福利在线观看| 国产色婷婷国产综合在线理论片a| 国内精品久久影院| 97国产suv精品一区二区62| 成人xxxx视频| 欧美巨大黑人极品精男| 国产91在线视频| 久久久精品视频在线观看| 精品久久久91| 亚洲激情中文字幕| 日韩中文理论片| 亚洲国产精品yw在线观看| 久久99精品久久久久久噜噜| 2019中文字幕全在线观看| 久久伊人精品一区二区三区| 日韩中文字幕免费| 宅男66日本亚洲欧美视频| 伊人亚洲福利一区二区三区| 国产精品久久久久久久app| 国产精品久久77777| 亚洲人成在线观看| 亚洲成人精品视频在线观看| 国产精品久在线观看| 91精品国产乱码久久久久久久久| 欧美亚洲在线视频| 亚洲国产精品久久久久秋霞蜜臀| 69久久夜色精品国产69| 日韩亚洲精品视频| 性色av一区二区三区| 久久99精品久久久久久琪琪| 国产一区二区香蕉| 中文字幕亚洲无线码a| 久久亚洲影音av资源网| 国产一区二区日韩| 日韩av影视在线| 色综合天天狠天天透天天伊人| 狠狠躁天天躁日日躁欧美| 亚洲成人黄色网| 亚洲美女激情视频| 欧美在线视频播放| 国产视频一区在线| 狠狠色狠狠色综合日日小说| 国产日产欧美精品| 国产精品久久久久久久久久久不卡| 日韩一区二区精品视频| 久久久久久久久亚洲| 51色欧美片视频在线观看| 最近的2019中文字幕免费一页| 亚洲激情 国产| 国产精品吊钟奶在线| 最近2019年中文视频免费在线观看| 欧美大奶子在线| 亚洲午夜未删减在线观看| 亚洲欧美日韩第一区| 日韩有码在线电影| 国产精品嫩草影院久久久| 亚洲综合精品一区二区| 国产精品综合久久久| 欧美情侣性视频| 国内精品免费午夜毛片| 成人免费观看a| 精品国产精品自拍| 久久人人爽人人爽人人片av高清| 在线观看国产精品淫| 国产91精品最新在线播放| 色爱精品视频一区| 91tv亚洲精品香蕉国产一区7ujn| 久久综合电影一区| 欧美丰满少妇xxxxx做受| 国产精品jizz在线观看麻豆| 国产欧美日韩中文字幕在线| 亚洲毛茸茸少妇高潮呻吟| 久久成人av网站| 91九色国产社区在线观看| 亚洲一区二区久久久久久久| 午夜精品视频网站| 国产日本欧美在线观看| 狠狠躁天天躁日日躁欧美| 日本欧美在线视频| 91精品视频在线免费观看| 国产精品大片wwwwww| 亚洲三级黄色在线观看| 久久久精品在线| 日韩av在线精品| 亚洲精品中文字| 亚洲国产古装精品网站| 中文字幕亚洲一区二区三区| 97在线视频免费| 国产日韩精品在线| 久久国产天堂福利天堂| 久久影院中文字幕| 欧美午夜www高清视频| 57pao国产成人免费| 97国产真实伦对白精彩视频8| 欧美日韩国产麻豆| 97视频在线播放| 欧美另类69精品久久久久9999| 久久手机精品视频| 成人春色激情网| 中文字幕久久久av一区| 日韩中文理论片| 日韩av网址在线| 日韩中文字幕视频在线| 午夜精品理论片| 久久精品久久久久| 国产精品av在线| 精品少妇v888av| 欧美视频中文字幕在线| 欧美国产日韩一区二区| 日韩一级黄色av| 日韩欧美亚洲一二三区| 久久影院中文字幕| 亚洲成人免费网站| 成人97在线观看视频| 精品欧美激情精品一区| 欧美一区深夜视频| 国产成人一区二区在线| 国产成人啪精品视频免费网| 亚洲国产精品久久久久| 亚洲性xxxx| 欧美区在线播放| 日本亚洲精品在线观看| 欧美在线免费观看| 国产精品久久一区| 777国产偷窥盗摄精品视频| 久久亚洲影音av资源网| 国产精品高清在线观看| 国产视频精品一区二区三区| 欧美有码在线观看| 色综合天天综合网国产成人网| 最近中文字幕mv在线一区二区三区四区| 一区二区三区美女xx视频| 姬川优奈aav一区二区| 精品少妇v888av| 欧美日韩一区二区在线| 欧美激情一区二区三区高清视频| 欧美精品成人91久久久久久久| 亚洲天堂视频在线观看| 九九精品视频在线观看| 亚洲级视频在线观看免费1级| 91精品国产91久久久久久| 欧美大尺度电影在线观看| 欧美肥臀大乳一区二区免费视频| 亚洲第一av在线|