Update source

This commit is contained in:
Star Brilliant 2013-09-15 13:11:58 +08:00
parent 54dec22616
commit 1bb8360c07

View File

@ -1,16 +1,85 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import colorsys
import logging import logging
import math
import sys import sys
import xml.dom.minidom import xml.dom.minidom
def ProcessComments(comments, f, width, height, bottomReserved, fontface, fontsize, reduced): def ProcessComments(comments, f, width, height, bottomReserved, fontface, fontsize, lifetime, reduced):
rows = [None]*(height-bottomReserved) WriteASSHead(f, width, height, fontface, fontsize)
rows = [[None]*(height-bottomReserved), [None]*(height-bottomReserved), [None]*(height-bottomReserved)]
for i in comments:
row = 0
rowmax = height-bottomReserved-i[7]
while row < rowmax:
freerows = TestFreeRows(rows, i, row, width, height, bottomReserved, lifetime)
if freerows >= i[7]:
MarkCommentRow(rows, i, row)
WriteComment(f, i, row, width, height, bottomReserved, fontsize, lifetime)
break
else:
row += freerows or 1
else:
if not reduced:
row = FindAlternativeRow(rows, i, height, bottomReserved)
MarkCommentRow(rows, i, row)
WriteComment(f, i, row, width, height, bottomReserved, fontsize, lifetime)
def TestFreeRows(rows, c, row, width, height, bottomReserved, lifetime):
res = 0
rowmax = height-bottomReserved-c[7]
while row < rowmax and res < c[7]:
if c[4] in (1, 2):
if rows[c[4]][row] and rows[c[4]][row][0]+lifetime > c[0]:
break
else:
if rows[c[4]][row] and rows[c[4]][row][0]+lifetime*c[8]/width > c[0]:
break
row += 1
res += 1
return res
def FindAlternativeRow(rows, c, height, bottomReserved):
res = 0
for row in range(height-bottomReserved-math.ceil(c[7])):
if not rows[c[4]][row]:
return row
elif rows[c[4]][row][1] < rows[c[4]][res][1]:
res = row
return res
def MarkCommentRow(rows, c, row):
for i in range(row, row+math.ceil(c[7])):
rows[c[4]][i] = c
def WriteComment(f, c, row, width, height, bottomReserved, fontsize, lifetime):
text = c[3].replace('\\', '\\\\').replace('\n', '\\n')
if c[4] == 1:
styles = '{\\a2}{\\pos(%(halfwidth)s, %(row)s)}' % {'halfwidth': round(width/2), 'row': row}
elif c[4] == 2:
styles = '{\\a6}{\\pos(%(halfwidth)s, %(row)s)}' % {'halfwidth': round(width/2), 'row': ConvertType2(row, height, bottomReserved)}
else:
styles = '{\\a1}{\\move(%(width)s, %(row)s, %(neglen)s, %(row)s)}' % {'width': width, 'row': row, 'neglen': -math.ceil(c[8])}
if -1 < c[6]-fontsize < 1:
styles += '{\\fs%s}' % round(c[6])
if c[5] != 0xffffff:
styles += '{\\c&H%02X%02X%02x&}' % (c[5]&0xff, (c[5]>>8)&0xff, (c[5]>>16)&0xff)
if c[5] == 0x000000:
styles += '{\\3c&HFFFFFF&}'
else:
styles += '{\\bord0}'
f.write('Dialogue: 3,%(start)s,%(end)s,Default,,0000,0000,0000,,%(styles)s%(text)s\n' % {'start': ConvertTimestamp(c[0]), 'end': ConvertTimestamp(c[0]+lifetime), 'styles': styles, 'text': text})
def ReadCommentsBilibili(f, fontsize): def ReadCommentsBilibili(f, fontsize):
'Output format: [(timeline, timestamp, no, comment, type, color, size, height)]' 'Output format: [(timeline, timestamp, no, comment, type, color, size, height, width)]'
dom = xml.dom.minidom.parse(f) dom = xml.dom.minidom.parse(f)
comment_element = dom.getElementsByTagName('d') comment_element = dom.getElementsByTagName('d')
i = 0 i = 0
@ -20,19 +89,59 @@ def ReadCommentsBilibili(f, fontsize):
assert len(p) >= 8 assert len(p) >= 8
assert p[1] in ('1', '4', '5') assert p[1] in ('1', '4', '5')
c = str(comment.childNodes[0].wholeText).replace('/n', '\\n') c = str(comment.childNodes[0].wholeText).replace('/n', '\\n')
yield (float(p[0]), int(p[4]), i, c, {'1': 0, '4': 2, '5': 1}[p[1]], int(p[3]), int(p[2])/25.0, (c.count('\\n')+1)*int(p[2])/25.0) size = int(p[2])*fontsize/25.0
yield (float(p[0]), int(p[4]), i, c, {'1': 0, '4': 2, '5': 1}[p[1]], int(p[3]), size, (c.count('\\n')+1)*size, CalculateLength(c)*size)
i += 1 i += 1
except (AssertionError, AttributeError, IndexError, TypeError, ValueError): except (AssertionError, AttributeError, IndexError, TypeError, ValueError):
logging.warning('Invalid comment: %s' % comment.toxml()) logging.warning('Invalid comment: %s' % comment.toxml())
continue continue
def ConvertTimestamp(timestamp):
hour, minute = divmod(timestamp, 3600)
minute, second = divmod(minute, 60)
centsecond = int(second*100.0)
return '%d:%02d:%02d.%02d' % (int(hour), int(minute), int(second), centsecond)
def WriteASSHead(f, width, height, fontface, fontsize):
f.write(
'''[Script Info]
ScriptType: v4.00+
Collisions: Normal
PlayResX: %(width)s
PlayResY: %(height)s
[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, %(fontface)s, %(fontsize)s, &H00FFFFFF, &H00FFFFFF, &H00000000, &H00000000, 0, 0, 0, 0, 100, 100, 0.00, 0.00, 1, 1, 0, 2, 20, 20, 20, 0
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
''' % {'width': width, 'height': height, 'fontface': fontface, 'fontsize': fontsize}
)
def NeedWhiteBorder(rgb):
h, l, s = colorsys.rgb_to_hls(((rgb>>16)&0xff)/255, ((rgb>>8)&0xff)/255, (rgb&0xff)/255)
return (1/12 < h < 7/12 and l < 1/3) or l < 5/12
def CalculateLength(s):
return max(map(len, s.split('\\n')))
def ConvertType2(row, height, bottomReserved):
return height-bottomReserved-row
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-o', '--output', metavar='OUTPUT', help='Output file') parser.add_argument('-o', '--output', metavar='OUTPUT', help='Output file')
parser.add_argument('-s', '--size', metavar='WIDTHxHEIGHT', required=True, help='Stage size in pixels') parser.add_argument('-s', '--size', metavar='WIDTHxHEIGHT', required=True, help='Stage size in pixels')
parser.add_argument('-fn', '--font', metavar='FONT', help='Specify font face', default='黑体') parser.add_argument('-fn', '--font', metavar='FONT', help='Specify font face', default='黑体')
parser.add_argument('-fs', '--fontsize', metavar='SIZE', help='Default font size', type=float, default=25.0) parser.add_argument('-fs', '--fontsize', metavar='SIZE', help='Default font size', type=float, default=25.0)
parser.add_argument('-l', '--lifetime', metavar='SECONDS', help='Duration of comment display', type=float, default=5.0)
parser.add_argument('-p', '--protect', metavar='HEIGHT', help='Reserve blank on the bottom of the stage', type=int, default=0) parser.add_argument('-p', '--protect', metavar='HEIGHT', help='Reserve blank on the bottom of the stage', type=int, default=0)
parser.add_argument('-r', '--reduce', action='store_true', help='Reduce the amount of danmakus if stage is full') parser.add_argument('-r', '--reduce', action='store_true', help='Reduce the amount of danmakus if stage is full')
parser.add_argument('file', metavar='FILE', nargs='+', help='Comment file to be processed') parser.add_argument('file', metavar='FILE', nargs='+', help='Comment file to be processed')
@ -53,6 +162,6 @@ if __name__ == '__main__':
else: else:
fo = sys.stdout fo = sys.stdout
comments.sort() comments.sort()
ProcessComments(comments, fo, width, height, args.protect, args.font, args.fontsize, args.reduce) ProcessComments(comments, fo, width, height, args.protect, args.font, args.fontsize, args.lifetime, args.reduce)
if args.output: if args.output:
fo.close() fo.close()