亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

bilibili彈幕轉(zhuǎn)ass程序制作思路及過程

 更新時(shí)間:2014年09月11日 10:52:43   投稿:hebedich  
本文主要是為了方便線下播放Bilibili的彈幕,而專門制作的一款將彈幕轉(zhuǎn)換為ASS的程序,介紹了程序制作的思路及過程,有需要的朋友可以參考下

b站的彈幕,線下播放還是挺麻煩的,專用的彈幕播放器對(duì)其他格式的視頻支持不好。我也試著弄個(gè)彈幕轉(zhuǎn)字幕的小程序出來(lái)。

抓取xml文件的工作就不多說(shuō)了,很簡(jiǎn)單的事,只要在播放頁(yè)面看看源文件就能確定xml文件的地址進(jìn)行抓取了。

本文主要是講述xml內(nèi)的彈幕轉(zhuǎn)字幕的過程。

除去xml文件開頭結(jié)尾的一些七七八八的東西,彈幕主體是這樣的:

<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包來(lái)解碼,但是丫把彈幕的屬性都放在p里面了,所以我使用正則表達(dá)式來(lái)提取的。

以上表第一條彈幕為例。很明顯的,p屬性開始的浮點(diǎn)數(shù),與播放時(shí)一比對(duì),就能知道,表示的是彈幕應(yīng)該出現(xiàn)的播放時(shí)間。

隨后的1和25先不管;

16777215,目測(cè)應(yīng)該是顏色(因?yàn)樵撝当硎緸槭M(jìn)制是FFFFFF);

1408852480,在彈幕中是遞增的,感覺應(yīng)該是個(gè)unix時(shí)間,用這個(gè)數(shù)(d),求:d/86400/365.2425+1970,結(jié)果約為2014.6??磥?lái)確實(shí)是unix時(shí)間。估計(jì)是創(chuàng)建彈幕的時(shí)間。

0,不知道,抓取了很多視頻的彈幕,這個(gè)位置都是0,暫且不管它。

7fa769b4,估計(jì)是創(chuàng)建者的ID,因?yàn)橥粁ml文件會(huì)出現(xiàn)多次,而且看起來(lái)是十六進(jìn)制數(shù),恰好有些hash函數(shù)就是返回4字節(jié)整數(shù)。

576008622,也是遞增的,不用猜也知道,這個(gè)肯定就是彈幕的ID了。

事后再核對(duì)一下,果然,1代表彈幕的類型(從右向左移動(dòng)啊,出現(xiàn)在下方或者上方啊……),25是字體大小,16777125是字體顏色。

所以,我們就只要捕獲每條彈幕的時(shí)間、類型、大小、顏色、文本就行了。

正則表達(dá)式:

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


捕獲彈幕很簡(jiǎn)單,關(guān)鍵是排布彈幕為字幕的算法。
關(guān)于這個(gè)算法我就很坑爹的弄了個(gè)亂七八糟的算法,采用的是固定移動(dòng)速度,最小重疊的排布原則。

對(duì)游動(dòng)彈幕,會(huì)傾向于選擇下面一行的位置,如果會(huì)重疊,則選擇更下一行(最低行會(huì)循環(huán)到最上面一行),如果沒有不重疊的行,會(huì)選擇重疊文本最少的行。

對(duì)上現(xiàn)隱/下現(xiàn)隱的固定彈幕,會(huì)選擇最接近上方/下方,且不重疊的行;如果沒有不重疊的行,則選擇重疊時(shí)間最短的行,居中放置字幕。

默認(rèn)字體微軟雅黑,默認(rèn)大小25,默認(rèn)白色黑邊;默認(rèn)占滿整個(gè)屏幕,共計(jì)12行;默認(rèn)屏幕大小640x360。

這么弄,主要是為了讓ass字幕的效果更接近原始彈幕的效果。

高級(jí)彈幕真的超出我的能力范圍了,全部忽略掉。

go源代碼如下:

// 將bilibili的xml彈幕文件轉(zhuǎn)換為ass字幕文件。
// xml文件中,彈幕的格式如下:
// <d p="32.066,1,25,16777215,1409046965,0,017d3f58,579516441">地板好評(píng)</d>
// p的屬性為時(shí)間、彈幕類型、字體大小、字體顏色、創(chuàng)建時(shí)間、?、創(chuàng)建者ID、彈幕ID。
// p的屬性中,后4項(xiàng)對(duì)ass字幕無(wú)用,舍棄。被<d>和</d>包圍的是彈幕文本。
// 只處理右往左、上現(xiàn)隱、下現(xiàn)隱三種類型的普通彈幕。
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>`)
 
// 用來(lái)保管彈幕的信息
type Danmu struct {
  text string
  time float64
  kind byte
  size int
  color int
}
 
// 使[]Danmu實(shí)現(xiàn)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]
}
 
// 將正則匹配到的數(shù)據(jù)填寫入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])
}
 
// 返回文本的長(zhǎng)度,假設(shè)ascii字符都是0.5個(gè)字長(zhǎng),其余都是1個(gè)字長(zhǎng)
func length(s string) float64 {
  l := 0.0
  for _, r := range s {
    if r < 127 {
      l += 0.5
    } else {
      l += 1
    }
  }
  return l
}
 
// 生成時(shí)間點(diǎn)的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,采用的簡(jiǎn)單的固定移速、最小重疊排布算法
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: // 下現(xiàn)隱
      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: // 上現(xiàn)隱
      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)
    }
  }
}
 
// 主函數(shù),實(shí)現(xiàn)了命令行
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更新:對(duì)字體排布進(jìn)行了修正。

2014.9.2 9:50am更新:算法修改為固定出現(xiàn)時(shí)間,最小重疊排布,最終版本。

over。歡迎各位評(píng)論,倒不如各位多多評(píng)論啊。

相關(guān)文章

  • YOLO v4常見的非線性激活函數(shù)詳解

    YOLO v4常見的非線性激活函數(shù)詳解

    這篇文章主要介紹了YOLO v4常見的非線性激活函數(shù),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-05-05
  • 怎樣制作“別人家的”Chrome插件

    怎樣制作“別人家的”Chrome插件

    Chrome插件有很多實(shí)用API可以讓我們使用,通過Chrome插件我們還可以做很多的事情,例如翻譯網(wǎng)頁(yè)文字等。本文將教你怎樣制作“別人家的”Chrome插件,感興趣的小伙伴一起來(lái)看看吧
    2021-08-08
  • HBuilder打包App方法(圖文教程)

    HBuilder打包App方法(圖文教程)

    下面小編就為大家?guī)?lái)一篇HBuilder打包App方法(圖文教程)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧
    2017-09-09
  • Scala函數(shù)式編程專題--函數(shù)思想介紹

    Scala函數(shù)式編程專題--函數(shù)思想介紹

    這篇文章主要介紹了Scala函數(shù)式編程的的相關(guān)資料,文中講解非常細(xì)致,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • ascii碼表(二進(jìn)制 十進(jìn)制 十六進(jìn)制)詳細(xì)介紹

    ascii碼表(二進(jìn)制 十進(jìn)制 十六進(jìn)制)詳細(xì)介紹

    這篇文章主要介紹了ascii碼表二進(jìn)制 十進(jìn)制 十六進(jìn)制詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下
    2017-01-01
  • 分享下手機(jī)軟件界面設(shè)計(jì)淺析

    分享下手機(jī)軟件界面設(shè)計(jì)淺析

    手機(jī)的軟件系統(tǒng)已成為用戶直接操作和應(yīng)用的主體,它應(yīng)以美觀實(shí)用、操作便捷為用戶所青睞。用戶界面設(shè)計(jì)的規(guī)范性顯得尤為重要
    2014-05-05
  • 分享5個(gè)實(shí)用的vs調(diào)試技巧

    分享5個(gè)實(shí)用的vs調(diào)試技巧

    vs 是我們平時(shí)編寫代碼時(shí)的首選工具,相信也是大多數(shù)小伙伴兒的首選調(diào)試工具,因?yàn)閷?shí)際操作起來(lái)太方便了,代碼編寫完,一個(gè) F5 就可以愉快的調(diào)試了。今天我想向大家推薦幾個(gè)非常值得了解 vs 調(diào)試技巧。
    2020-09-09
  • 深入分析java與C#底層控制能力區(qū)別及示例詳解

    深入分析java與C#底層控制能力區(qū)別及示例詳解

    這篇文章主要為大家深入分析java與C#底層控制能力不同的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2021-11-11
  • 建模UML用例圖使用原理總結(jié)分析

    建模UML用例圖使用原理總結(jié)分析

    這篇文章主要介紹了建模UML用例圖使用原理總結(jié)分析,文中對(duì)uml所包含的元素以及關(guān)系類型作了詳細(xì)的概括,有需要的朋友可以借鑒參考下
    2021-09-09
  • 趣味函數(shù)式編程圣經(jīng)

    趣味函數(shù)式編程圣經(jīng)

    這篇文章主要介紹了函數(shù)式編程的的相關(guān)資料,有趣的講解了函數(shù)式編程的相關(guān)知識(shí),幫助大家更好的理解學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06

最新評(píng)論