2019独角兽企业重金招聘Python工程师标准>>>

抓取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 mainimport ("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  stringtime  float64kind  bytesize  intcolor int
}// 使[]Danmu实现sort.Interface接口,以便排序
type Danmus []Danmufunc (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.0for _, 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 := 0max := 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 % 36m, k := f+10000, 0for j := 0; j < 36; j += d {t := (i + j) % 36if n := max(p[t : t+d]); n <= f {k = tbreak} else if m > n {k = tm = 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*28case 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 - 1s += 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。欢迎各位评论,倒不如各位多多评论啊。

转载于:https://my.oschina.net/liudiwu/blog/308924

bilibili弹幕转ass相关推荐

  1. 菜鸟弟弟从零开始的爬取Bilibili弹幕的Python爬虫教程-哔哩哔哩 - ( ゜- ゜)つロ 干杯~

    从零开始的爬取Bilibili弹幕的Python爬虫教程 或许可以作为一个爬虫小白的练手的demo? 还是先看看什么是爬虫吧!(还有Bilibili! ) 网络爬虫: 网络爬虫(又称为网页蜘蛛,网络机 ...

  2. python爬取bilibili弹幕_Python爬虫爬取Bilibili弹幕过程解析

    先来思考一个问题,B站一个视频的弹幕最多会有多少? 比较多的会有2000条吧,这么多数据,B站肯定是不会直接把弹幕和这个视频绑在一起的. 也就是说,有一个视频地址为https://www.bilibi ...

  3. android bilibili弹幕技术解析,bilibili弹幕定位

    // ==UserScript== // @name bilibili弹幕定位 // @namespace http://tampermonkey.net/ // @version 1.3 // @d ...

  4. 怎么用python发弹幕_[python]bilibili弹幕发送者查询器软件

    简介: 一款用于查询bilibili弹幕发送者的软件,输入视频号(需要保证视频有效且有弹幕)后对弹幕池中的弹幕按照发送时间(从新到旧的顺序)进行查询.操作简单,需要联网使用. 查询结果以uid格式展示 ...

  5. python爬取bilibili弹幕_Python抓取BiliBili拦河坝并生成单词云,爬取,bilibili,弹幕

    木秀于林,风亦惧之:虽为萤火,亦绽光芒. Python爬取bilibili弹幕并生成词云 目标网站: 哔哩哔哩: https://www.bilibili.com/ 我这里以 TES vs SN 第四 ...

  6. Python爬虫:爬取Bilibili弹幕过程示例代码

    这篇文章主要介绍了Python爬虫爬取Bilibili弹幕过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 先来思考一个问题,B站一个视频的弹 ...

  7. 今天分享个用Python爬虫爬取Bilibili弹幕的小例子解析

    先来思考一个问题,B站一个视频的弹幕最多会有多少? 比较多的会有2000条吧,这么多数据,B站肯定是不会直接把弹幕和这个视频绑在一起的. 也就是说,有一个视频地址为 https://www.bilib ...

  8. bilibili怎么设置弹幕数量_python爬虫:bilibili弹幕爬取+词云生成

    如果你懒得看下边的文字,我录了一个完整的教学视频在b站上. 我的B站教学:https://www.bilibili.com/video/av75377135?p=2 工作原理 b站是提供弹幕接口的,所 ...

  9. python爬取bilibili弹幕_python爬虫:bilibili弹幕爬取+词云生成

    如果你懒得看下边的文字,我录了一个完整的教学视频在b站上. 我的B站教学:https://www.bilibili.com/video/av75377135?p=2 工作原理 b站是提供弹幕接口的,所 ...

  10. bilibili 解析_用 Python 抓取 bilibili 弹幕并分析!

    时隔一年,嵩哥带来他的新作<雨幕>. 他依旧认真创作,追求高品质,作品在发表之前已听了五百遍以上. 如此高品质的音乐,大家如何评价呢?通过哔哩哔哩上的视频弹幕,感受一下.01 实现思路 首 ...

最新文章

  1. CDays–4 习题六(修改文本)及相关内容解析。
  2. java linux urlencode_iOS urlEncode编码解码(非过时方法,已解决)
  3. AJAX跨域请访问的问题
  4. WebPack在多页应用项目中的探索
  5. VMware workstation 8.0上安装VMware ESXI5.0
  6. 高效的企业测试-单元和用例测试(2/6)
  7. 程序员利用测试账户套现千万美元,或面临20年监禁
  8. 一、Team Explorer的安装
  9. idea js检查太卡_IntelliJ IDEA抑制、禁用与启用检查
  10. 常用数据库优化方案(五)
  11. windows 下搭建 git 服务器 gogs
  12. java创建线程的三种方式及其对照
  13. 蓝牙AVRCP协议分析
  14. 李运华老师的一些经典见解收藏
  15. 利用JNI调用c++函数
  16. 使用consul注册中心要求服务以自定义instance-id形式注册
  17. 怎么删除TEMP文件夹
  18. C++ SLT中的容器学习与函数谓词
  19. 数据库之Mysql索引、事务与存储引擎
  20. Android adb的使用

热门文章

  1. java-将xlsx(excel)文件转换成json
  2. linux新建用户切换后显示-bash-4.1$(转载)
  3. TeamViewer运行在Windows Server 2008下连接时错误提示:正在初始化显示参数
  4. 禁止微信调整页面字体大小
  5. HDU 6124 Euler theorem
  6. ORA-01403: no data found
  7. Scala基础 - 函数和方法的区别
  8. 二、2.4版本之前的apache的安装
  9. CCNA认证(2)--网络互联基础
  10. go 时间格式风格详解