以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 XSL/XSLT/XSL-FO/CSS 』  (http://bbs.xml.org.cn/list.asp?boardid=8)
----  [原创]XSLT实现SRT电影字幕修正  (http://bbs.xml.org.cn/dispbbs.asp?boardid=8&rootid=&id=66009)


--  作者: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) &gt; 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) &gt; 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) &gt; 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 &lt; 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="' --&gt; '"/><!--箭头-->
                        <xsl:value-of select="substring-after($sb1,'&gt; ')"/>

                        <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 &lt; 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) &lt; $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 &gt; -1) and (number($u)+$ms &gt; 1000)">
                                <xsl:value-of select="floor((number($u)+$ms) div 1000)"/>
                        </xsl:when>
                        <xsl:when test="number($u)+$ms &lt; 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 &gt; 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 &gt; 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编辑过]

--  作者:ioyichen
--  发布时间:8/18/2008 5:46:00 PM

--  
呵呵,QR的帖子要顶
W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
6,007.813ms