您的位置:首页 > Web前端 > JavaScript

acfun json 弹幕 转换 bilibili xml 弹幕

2015-07-14 17:33 841 查看
以往一直使用 acdown 自带的 acplay 功能看 acfun 的视频, 然而 acdown 已经停止更新, 并且 acfun 又一次改版。。。

因此想继续用 acplay, 只能把A站的弹幕转换为B站的格式看了。

首先, 想要转换弹幕文件, 我们首先要下载解析出A站和B站的弹幕。 关于如何获取弹幕, 参见https://tiansh.github.io/的ASS 项目。 具体来说, 就是提取cid, 然后下载弹幕。

我借鉴了该项目下载弹幕的程序。 分别是:

A站

// 获取弹幕id
var getVid = function (callback) {
  var player, m, vid = null;
  try {
    player = document.querySelector('iframe#ACFlashPlayer-re');
    m = player.src.match(/vid=(\d+)/);
    vid = Number(m[1]);
  } catch (e) { }
  if (!vid) try {
    player = document.querySelector('object#ACFlashPlayer-re');
    m = player.querySelector('param[name="flashvars"]').getAttribute('value').match(/videoId=(\d+)/);
    vid = Number(m[1]);
  } catch (e) { }
  if (!vid) setTimeout(function () {
    getVid(callback);
  }, 1000); else callback(vid);
};

// 通过弹幕id获取弹幕内容
// 弹幕内容是A站直接提供的数据
var getDanmaku = function (vid, callback) {
  GM_xmlhttpRequest({
    'method': 'GET',
    // FIXME 最后可能需要个弹幕上限,不过就先这样吧
    'url': 'http://static.comment.acfun.mm111.net/' + vid,
    'onload': function (resp) {
      var data;
      try {
        data = JSON.parse(resp.responseText);
        data = data.reduce(function (x, y) { return x.concat(y); });
      } catch (e) { data = null; }
      if (!data || typeof data.length !== 'number') callback(vid);
      else callback(vid, data);
    },
    'onerror': function () {
      callback(getVid);
    },
  });
};

// 将弹幕内容转换为程序内部的表达方式
var mina = function (vid, danmaku) {
  danmaku = danmaku.map(function (line) {
    var info = line.c.split(','), text = line.m;
    return {
      'text': text,
      'time': Number(info[0]),
      'color': RRGGBB(Number(info[1])),
      'mode': [undefined, 'R2L', undefined, undefined, 'BOTTOM', 'TOP'][Number(info[2])],
      'size': Number(info[3]),
      'bottom': false,
      // 'sender': String(info[4]),
      // 'create': new Date(Number(info[5]) * 1000),
    };
  });
  var name;
  try { name = document.querySelector('#txt-title-view').textContent; }
  catch (e) { name = '' + vid; }
  var ass = generateASS(setPosition(danmaku), {
    'title': document.title,
    'ori': location.href,
  });
  startDownload('\ufeff' + ass, name + '.ass');
};


B站:

/*
 * bilibili
 */

// 获取xml
var fetchXML = function (cid, callback) {
  GM_xmlhttpRequest({
    'method': 'GET',
    'url': 'http://comment.bilibili.com/{{cid}}.xml'.replace('{{cid}}', cid),
    'onload': function (resp) {
      var data = (new DOMParser()).parseFromString(resp.responseText, 'text/xml');
      var danmaku = Array.apply(Array, data.querySelectorAll('d')).map(function (line) {
        var info = line.getAttribute('p').split(','), text = line.textContent;
        return {
          'text': text,
          'time': Number(info[0]),
          'mode': [undefined, 'R2L', 'R2L', 'R2L', 'BOTTOM', 'TOP'][Number(info[1])],
          'size': Number(info[2]),
          'color': RRGGBB(Number(info[3])),
          'bottom': Number(info[5]) > 0,
          // 'create': new Date(Number(info[4])),
          // 'pool': Number(info[5]),
          // 'sender': String(info[6]),
          // 'dmid': Number(info[7]),
        };
      });
      callback(danmaku);
    }
  });
};

// 获取当前cid
var getCid = function (callback) {
  debug('get cid...');
  var cid = null, src = null;
  try {
    src = document.querySelector('#bofqi iframe').src.replace(/^.*\?/, '');
    cid = Number(src.match(/cid=(\d+)/)[1]);
  } catch (e) { }
  if (!cid) try {
    src = document.querySelector('#bofqi embed').getAttribute('flashvars');
    cid = Number(src.match(/cid=(\d+)/)[1]);
  } catch (e) { }
  if (!cid) try {
    src = document.querySelector('#bofqi object param[name="flashvars"]').getAttribute('value');
    cid = Number(src.match(/cid=(\d+)/)[1]);
  } catch (e) { }
  if (cid) setTimeout(callback, 0, cid);
  else if (src) GM_xmlhttpRequest({
    'method': 'GET',
    'url': 'http://interface.bilibili.com/player?' + src,
    'onload': function (resp) {
      try { cid = Number(resp.responseText.match(/<chatid>(\d+)<\/chatid>/)[1]); }
      catch (e) { }
      setTimeout(callback, 0, cid || undefined);
    },
    'onerror': function () { setTimeout(callback, 0); }
  }); else {
    setTimeout(getCid, 100, callback);
  }
};


在此向原作者致敬!

接下来, 我们下载了acun和bilibili的json和xml格式, 那么简单, 就是转换文件而已。 在此我用了 python 做 json转换。

代码本身很简单:

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

file = open('C:\\Users\\Administrator\\Desktop\\ab\\2.json')

try:
    content = file.read()
finally:
    file.close()

import json

jcon = json.loads(content, encoding='GB2312')

f2 = open('C:\\Users\\Administrator\\Desktop\\ab\\2.xml', 'w')
f2.write('<?xml version="1.0" encoding="UTF-8"?>\n')
f2.write('<i><chatserver>chat.bilibili.com</chatserver><chatid>3978802</chatid><mission>0</mission><maxlimit>1000</maxlimit><source>k-v</source>')

jcs = jcon[0]
for jc in jcs:
	a = jc['m']
	b = jc['c']
	
	# c: time, color, mode, size, sender, create, ?
	c = b.split(',')
	
	f2.write('<d p="')
	
	# time
	str = '%s,' % (c[0])
	f2.write(str)
	
	# mode
	str = '%s,' % (c[2])
	f2.write(str)
	
	# size
	str = '%s,' % (c[3])
	f2.write(str)
	
	# color
	str = '%s,' % (c[1])
	f2.write(str)
	
	# create
	str = '%s,' % (c[5])
	f2.write(str)
	
	# pool
	f2.write('0,')
	
	# sender
	str = '%s,' % (c[4])
	f2.write(str)
	
	# dmid
	str = '%s">' % (c[6])
	f2.write(str)
	
	# text
	str = '%s</d>\n' % (a)
	f2.write(str)

f2.write('</i>')


代码不复杂, 但是有些问题值得记录。 首先是python的编码问题, 开始用json.loads()直接读的时候老出错, 后来才发现是编码问题没有处理好。 然后还有, 读取到txt之后, 可以print, 但输出文件的时候显示“'ascii' codec can't encode character u'\u5657' in position 0: ordinal not in range(128)”

搜索后发现, 写的时候也要指定编码。

参考了一下文章: http://hjc73.blog.163.com/blog/static/1046929201364115635197/
http://python3-cookbook.readthedocs.org/zh_CN/latest/c06/p02_read-write_json_data.html

over, enjoy it!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: