-- 作者:Qr
-- 发布时间:8/17/2008 8:50:00 PM
-- [原创]XSLT实现SRT电影字幕修正
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点》的字幕: *************************************** 1 00:00:08,480 --> 00:00:19,770 玩转21点 2 00:00:22,910 --> 00:00:31,900 21.REPACK 3 00:00:40,107 --> 00:00:42,632 Winner,winner,chicken dinner 4 00:00:43,911 --> 00:00:47,403 这句话整晚都在我的耳边盘旋 5 00:00:48,448 --> 00:00:50,780 我指的是那个词 拉斯维加斯赌场的习惯用语 6 00:00:51,585 --> 00:00:54,986 随便问一个以前的赌场老板 他们肯定知道 7 00:00:58,225 --> 00:01:02,662 这个习语的形成最早要归功于 一个在Binion赌场的中国商人 8 00:01:03,430 --> 00:01:06,399 他每赢一局的时候都要喊出那句话 9 00:01:06,500 --> 00:01:09,958 那已经是40年前的事了 可这个口头禅依然被使用 10 00:01:10,070 --> 00:01:12,061 Winner, 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 [此贴子已经被作者于2008-8-17 23:16:23编辑过]
|