[原创]XSLT实现SRT电影字幕修正 
2008/8/17 20:42:23
阅读全文(4828) | 回复(4) | 编辑 | 精华
XSLT实现SRT电影字幕修正 原创:Qr,8/17/2008 http://Qr.blogger.org.cn 喜欢BT电影的朋友经常会遇到这样的问题,在看电影的过程中,由于字幕的制作出现各种各样的错误,令人非常扫兴,最常见的是时间轴不匹配,不是提前就是延后,有时后面一条字幕包括时间截被当作当前字幕来输出。要修正这些错误,只有到网上搜索相关工具来调整和修复。在这里,让我们用XSLT去实现看似不可能完成的任务吧——XSLT实现SRT电影字幕修正!原创:Qr,8/17/2008 http://Qr.blogger.org.cn 首先我们先来看一段截取自电影《玩转21点》的字幕:***************************************100:00:08,480 --> 00:00:19,770玩转21点 200:00:22,910 --> 00:00:31,90021.REPACK 300:00:40,107 --> 00:00:42,632Winner,winner,chicken dinner 400:00:43,911 --> 00:00:47,403这句话整晚都在我的耳边盘旋 500:00:48,448 --> 00:00:50,780我指的是那个词拉斯维加斯赌场的习惯用语 600:00:51,585 --> 00:00:54,986随便问一个以前的赌场老板他们肯定知道 700:00:58,225 --> 00:01:02,662这个习语的形成最早要归功于一个在Binion赌场的中国商人 800:01:03,430 --> 00:01:06,399他每赢一局的时候都要喊出那句话 900:01:06,500 --> 00:01:09,958那已经是40年前的事了可这个口头禅依然被使用 1000:01:10,070 --> 00:01:12,061Winner, winner, chicken dinner这就是它的由来 ***************************************请注意,影片上出现的每行字幕,实际上格式为:***************序号(换行)时间轴(换行)字幕内容(换行)空白行***************因为格式固定,这就为我们通过XSLT实现SRT电影字幕修正奠定了算法的基础。正因为空白行的存在,我们可以通过它来作为XSLT处理每行字幕的依据。 原创:Qr,8/17/2008 http://Qr.blogger.org.cn 那么,到底怎么实现XSLT修正SRT电影字幕呢?首先还得程序语言帮忙,就是将SRT文件转换成规范的XML文档(至于转换方法,不同的程序语言方法不同,我就不一一介绍了,大家如果不会,可以自己去查资料)。转换后我们会得到这样一个XML文档:<?xml version="1.0" encoding="gb2312"?><!--encoding根据需要字义,这里我全部使用gb2312--><root>SRT文件内容</root> 原创:Qr,8/17/2008 http://Qr.blogger.org.cn SRT文件经过转换可以得到符合XML规范的文档,这样我们就可以通过XSLT去处理了,下面是XSL代码,纯粹是XSL命名模板和XPath字符串函数的应用,没有太多注释:<?xml version="1.0" encoding="gb2312"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="text" version="1.0" encoding="gb2312" indent="yes"/><!--srt实际上是文本文件,故取method="text"--> <xsl:variable name="ms" select="-490"/><!--ms以毫秒为单位,1s=1000ms--> <xsl:variable name="br" select="'
'"/> <xsl:template match="/"> <!--SRT末尾要保证有3个换行,否则取不到最末字幕。解决:在将文件内容读入DOM前通过程序语言添加3个换行符。或再加一个递归,条件是:string-length($msg) > 1 and not(contains($msg,'

'))--> <xsl:call-template name="msg"> <xsl:with-param name="msg" select="." /> <xsl:with-param name="pos" select="1" /> </xsl:call-template></xsl:template> <!--命名模板msg主要是通过两个换行符“

”为标记截取每行字幕--><xsl:template name="msg"> <xsl:param name="msg"/> <xsl:param name="pos"/> <xsl:variable name="sb" select="substring-before($msg,'

')"/> <!--以下两个xsl:if不能解决字幕间隔2n+1个换行符的问题(正常应该是2个换行符)--> <!--xsl:if test="string-length($sb) > 1"> <xsl:call-template name="msg1"> <xsl:with-param name="msg1" select="$sb" /> <xsl:with-param name="pos1" select="1" /> </xsl:call-template> </xsl:if> <xsl:if test="contains($msg,'

')"> <xsl:call-template name="msg"> <xsl:with-param name="msg" select="substring-after($msg,'

')" /> </xsl:call-template> </xsl:if--> <!--通过xsl:choose来能解决字幕间隔2n+1个换行符的问题(正常应该是2个换行符)--> <xsl:choose> <xsl:when test="starts-with($sb,'
')"><!--递归,跳过2n+1个换行符--> <xsl:call-template name="msg"> <xsl:with-param name="msg" select="substring-after($msg,'
')" /> <xsl:with-param name="pos" select="$pos" /> </xsl:call-template> </xsl:when> <xsl:when test="string-length($sb) > 1"> <xsl:call-template name="msg1"> <xsl:with-param name="msg1" select="$sb" /> <xsl:with-param name="pos1" select="1" /> <xsl:with-param name="pos" select="$pos" /> </xsl:call-template> <xsl:if test="contains($msg,'

')"> <!--递归,处理下一行字幕--> <xsl:call-template name="msg"> <xsl:with-param name="msg" select="substring-after($msg,'

')" /> <xsl:with-param name="pos" select="$pos+1" /> </xsl:call-template> </xsl:if> </xsl:when> </xsl:choose> </xsl:template> <!--命名模板msg1是将每行字幕通过
和pos1定位分割序号、时间轴和字幕文字--><xsl:template name="msg1"> <xsl:param name="msg1"/> <xsl:param name="pos1"/> <xsl:param name="pos"/> <xsl:variable name="sb1"> <xsl:choose> <xsl:when test="$pos1 < 3"> <xsl:value-of select="substring-before($msg1,'
')" /> </xsl:when> <xsl:otherwise> <xsl:value-of select="$msg1" /> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:choose> <xsl:when test="$pos1=1"> <xsl:value-of select="$sb1"/><!--序号--><xsl:value-of select="$br"/> <xsl:if test="$sb1 != $pos"> <!--命名模板msg2处理两条字幕间没有空白行的问题--> <!--xsl:call-template name="msg2"> <xsl:with-param name="msg2" select="substring-after($msg2,concat($pos,'
'))" /> <xsl:with-param name="pos2" select="pos2+1" /> <xsl:with-param name="pos" select="$pos+1" /> </xsl:call-template--> <xsl:message terminate="yes"> <xsl:value-of select="'message:ERROR'"/> </xsl:message> <!--正常情况下遇到xsl:message代码应该停止执行,但实际运行时这里出现两次错误信息,估计某个地方处理还有些问题--> </xsl:if> </xsl:when> <xsl:when test="$pos1=2"> <xsl:call-template name="timefix"> <xsl:with-param name="time" select="substring-before($sb1,' ')" /> <xsl:with-param name="pos" select="$pos" /> </xsl:call-template> <xsl:value-of select="' --> '"/><!--箭头--> <xsl:value-of select="substring-after($sb1,'> ')"/> <xsl:value-of select="$br"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$sb1"/><xsl:value-of select="concat($br,$br)"/> </xsl:otherwise> </xsl:choose> <xsl:if test="$pos1 < 3"> <xsl:call-template name="msg1"><!--递归,继续定位分割当前行字幕--> <xsl:with-param name="msg1" select="substring-after($msg1,'
')" /> <xsl:with-param name="pos1" select="$pos1+1" /> <xsl:with-param name="pos" select="$pos" /> </xsl:call-template> </xsl:if> </xsl:template> <!--命名模板timefix,处理时间轴--><xsl:template name="timefix"> <xsl:param name="time"/> <xsl:param name="pos"/> <xsl:variable name="h" select="substring($time,1,2)"/> <xsl:variable name="m" select="substring($time,4,2)"/> <xsl:variable name="s" select="substring($time,7,2)"/> <xsl:variable name="u" select="substring($time,10)"/> <xsl:variable name="u1"><!--求时间轴的毫秒部分与修正数值的和--> <xsl:choose> <xsl:when test="number($u) < $ms*-1"> <xsl:value-of select="format-number(((number($u)+$ms) mod 1000)+1000,'000')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="format-number((number($u)+$ms) mod 1000,'000')"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="uj"><!--求毫秒进位--> <xsl:choose> <xsl:when test="(number($u)+$ms > -1) and (number($u)+$ms > 1000)"> <xsl:value-of select="floor((number($u)+$ms) div 1000)"/> </xsl:when> <xsl:when test="number($u)+$ms < 0"> <xsl:value-of select="floor((number($u)+$ms) div 1000)"/> </xsl:when> <xsl:otherwise>0</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="s1" select="format-number((number($s)+$uj) mod 60,'00')"/><!--求秒--> <xsl:variable name="sj"><!--求秒进位--> <xsl:choose> <xsl:when test="number($s)+$uj > 60"> <xsl:value-of select="floor((number($s)+$uj) div 60)"/> </xsl:when> <xsl:otherwise>0</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="m1" select="format-number((number($m)+$sj) mod 60,'00')"/><!--求分--> <xsl:variable name="mj"><!--求分进位--> <xsl:choose> <xsl:when test="number($m)+$sj > 60"> <xsl:value-of select="floor((number($m)+$sj) div 60)"/> </xsl:when> <xsl:otherwise>0</xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="h1" select="format-number((number($h)+$mj) mod 24,'00')"/><!--求小时--> <xsl:value-of select="concat($h1,':',$m1,':',$s1,',',$u1)"/> </xsl:template> </xsl:stylesheet> 原创:Qr,8/17/2008 http://Qr.blogger.org.cn 前面的注释已经提到,srt实际上是文本文件,故xsl:output设定method="text",当程序语言通过XSL转换XML为文本格式的字符串并输出(这个也不是本文的重点,大家可以上网搜)后,将其后缀名定“.srt”就是我们修正后的字幕文件了。 原创:Qr,8/17/2008 http://Qr.blogger.org.cn 当然,细心的朋友会注意到代码中曾经定义了一个变量pos,但并没有发现其在代码中的作用。其实,它是为msg2定义的,目的是处理两条字幕间没有空白行的问题,但这个命名模板我并没有完成它,因为这样做并不是最好的,而且,最近一直在用SVG进行开发,时间上安排不过来。 原创:Qr,8/17/2008 http://Qr.blogger.org.cn 下面是我给出的XSLT修正SRT电影字幕另一种思路:命名模板msg不要以两个换行符“

”为标记截取字幕,而是以“序号+换行符(
)”为标记截取字幕,时间轴的处理同上。这样写代码会方便很多。 原创:Qr,8/17/2008 http://Qr.blogger.org.cn 另外,本文的思路和代码基本上是解决前面提到的两个问题,其实还能做得更多些,我这里仅仅个初步,感兴趣的朋友可以把它完善得更好。 原创:Qr,8/17/2008 http://Qr.blogger.org.cn 最后,要提的是<xsl:message terminate="yes|no"/>,当XSL解析XML出错时,可以用它来返回错误信息,程序语言同样可以接受这个错误信息的返回,这样可以提高程序的交互性能,但不同的程序语言,错误信息的捕捉不尽相同,大家可以根据实际情况查阅相关帮助文档。 原创:Qr,8/17/2008 http://Qr.blogger.org.cn P.S. 其实这篇博文的代码早在十天前就写好了,只是一直在写SVG代码,没时间完善。今后一段时间可能重点依然在SVG,所以XSL相关博文会少一些,反而可能会增加一些SVG相关学习笔记之类的博文。
Posted by Qr on 2008/8/17 20:42:23
回复:[原创]XSLT实现SRT电影字幕修正
2008/8/20 13:04:41
个人主页 | 引用回复 | 主人回复 | 返回 | 编辑 | 删除
羡慕qr的能力!
Posted by 半路和尚 on 2008/8/20 13:04:41
回复:[原创]XSLT实现SRT电影字幕修正
2008/8/19 9:18:44
个人主页 | 引用回复 | 主人回复 | 返回 | 编辑 | 删除
纯粹技术问题。实际上没那个必要自己写代码,网上工具一大堆。 其实XSLT功能真的很强大,写这篇博文目的是让更多人认识XSLT。
Posted by Qr on 2008/8/19 9:18:44
回复:[原创]XSLT实现SRT电影字幕修正
2008/8/18 21:39:14
个人主页 | 引用回复 | 主人回复 | 返回 | 编辑 | 删除
我比较懒,直接下了一个修正工具,呵呵~
Posted by 真不准 on 2008/8/18 21:39:14
回复:[原创]XSLT实现SRT电影字幕修正
2008/8/18 21:39:07
个人主页 | 引用回复 | 主人回复 | 返回 | 编辑 | 删除
我比较懒,直接下了一个修正工具,呵呵~
Posted by 真不准 on 2008/8/18 21:39:07
发表评论: |