这是苹果官方的subtitleWriter demo的系列教程。目前关于这部分的材料还不是很多,特地分析源码了解下。
demo下载地址:
https://developer.apple.com/library/mac/samplecode/avsubtitleswriterOSX/Listings/avsubtitleswriter_SubtitlesTextReader_m.html
这是第一篇,字幕解析。
首先引入一个subtitle类,这个类用于从字幕文件中提取字幕信息,并存取。
@interface Subtitle : NSObject
+ (instancetype)subtitleWithText:(NSString *)text timeRange:(CMTimeRange)timeRange forced:(BOOL)forced;
- (CMFormatDescriptionRef)copyFormatDescription;
- (CMSampleBufferRef)copySampleBuffer;
@property NSString *text;
@property CMTimeRange timeRange;
@property BOOL forced;
@property CMTextDisplayFlags displayFlags;
@end
第一个方法是会在SubtitlesTextReader 调用初始化方法initWithText中被调用,从字幕文件中解析出各部分,然后据此构造字幕对象。
第二第三个方法先留着,暂时不用。
initWithText中解析必要的信息
NSMutableArray *mutableSubtitles = [NSMutableArray array];
// Check for a language
NSRegularExpression *languageExpression = [NSRegularExpression regularExpressionWithPattern:@"language: (.*)" options:0 error:nil];
NSTextCheckingResult *languageResult = [languageExpression firstMatchInString:text options:0 range:NSMakeRange(0, [text length])];
_languageCode = [text substringWithRange:[languageResult rangeAtIndex:1]];
// Check for an extended language
NSRegularExpression *extendedLanguageExpression = [NSRegularExpression regularExpressionWithPattern:@"extended language: (.*)" options:0 error:nil];
NSTextCheckingResult *extendedLanguageResult = [extendedLanguageExpression firstMatchInString:text options:0 range:NSMakeRange(0, [text length])];
_extendedLanguageTag = [text substringWithRange:[extendedLanguageResult rangeAtIndex:1]];
// See if SDH has been requested
NSRegularExpression *characteristicsExpression = [NSRegularExpression regularExpressionWithPattern:@"characteristics:.*(SDH)" options:NSRegularExpressionCaseInsensitive error:nil];
NSTextCheckingResult *characteristicsResult = [characteristicsExpression firstMatchInString:text options:0 range:NSMakeRange(0, [text length])];
_wantsSDH = ([[text substringWithRange:[characteristicsResult rangeAtIndex:1]] caseInsensitiveCompare:@"SDH"] == NSOrderedSame) ? YES : NO;
这边的做法是有待商榷的,你不可能简单从文本去解析字幕的语言,可能需要识别,当然规范的情况下是可以这么做的。
这边解析出三个内容,一是语言代码,二是扩展语言标签,三是SDH标记。你可能不理解第二和第三,第二大概是辅助语言的意思,譬如一部电影,可能对白基本上是英语,但是偶尔出现点法语什么的,那么法语就算是extended language了。至于SDH标记,subtitle for deaf and hard of hearing 听力障碍提示字幕,具体不大清楚,可能就是不固定在下方的一些提示性的字幕吧。
当然这部分不是我们的重点,因为它可能并没多少实际的用处。
下面是解析字幕,是重点,也是有实际意义的。
// Find the subtitle time ranges and text
__block int forcedCount = 0;
NSRegularExpression *subtitlesExpression = [NSRegularExpression regularExpressionWithPattern:@"(..):(..):(..),(...) --> (..):(..):(..),(...)( !!!)?\n(.*)" options:0 error:nil];
[subtitlesExpression enumerateMatchesInString:text options:0 range:NSMakeRange(0, [text length]) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
// Get the text
NSString *subtitleText = [text substringWithRange:[result rangeAtIndex:10]];
// Create the time range
double startTime = ([[text substringWithRange:[result rangeAtIndex:1]] doubleValue] * 60.0 * 60.0) + ([[text substringWithRange:[result rangeAtIndex:2]] doubleValue] * 60.0) + [[text substringWithRange:[result rangeAtIndex:3]] doubleValue] + ([[text substringWithRange:[result rangeAtIndex:4]] doubleValue] / 1000.0);
double endTime = ([[text substringWithRange:[result rangeAtIndex:5]] doubleValue] * 60.0 * 60.0) + ([[text substringWithRange:[result rangeAtIndex:6]] doubleValue] * 60.0) + [[text substringWithRange:[result rangeAtIndex:7]] doubleValue] + ([[text substringWithRange:[result rangeAtIndex:8]] doubleValue] / 1000.0);
CMTimeRange timeRange = CMTimeRangeMake(CMTimeMakeWithSeconds(startTime, 600), CMTimeMakeWithSeconds(endTime - startTime, 600));
// Is it forced?
BOOL forced = NO;
if (([result rangeAtIndex:9].length > 0) && [[text substringWithRange:[result rangeAtIndex:9]] isEqualToString:@" !!!"])
{
forced = YES;
forcedCount++;
}
// Stash a Subtitle object for later use by -copyNextSampleBuffer
[mutableSubtitles addObject:[Subtitle subtitleWithText:subtitleText timeRange:timeRange forced:forced]];
}];
这里使用了正则表达式,其中
NSRegularExpression *subtitlesExpression = [NSRegularExpression regularExpressionWithPattern:@"(..):(..):(..),(...) --> (..):(..):(..),(...)( !!!)?\n(.*)" options:0 error:nil];
这一句是我们要匹配的表达式,代表了一行时间轴以及对应的字幕。每个括号代表一个子表达式。
接着,我们处理子表达式
[subtitlesExpression enumerateMatchesInString:text options:0 range:NSMakeRange(0, [text length]) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {}];
这个方法是获取一条字幕以时间轴,再在block中进行子查询。所有子表达式的结果存放在result中,它其实可以认为是个NSRange数组。使用rangeatindex取出每个子表达式匹配的结果,结果是一个nsrange,反应了它在字符串中的位置和长度。值得注意的是,index 0代表的是整条正则的匹配结果,从1开始才是子表达式的匹配结果。
在本例中,前面四个括号分别对应index 1,2,3,4,表示时,分,秒,毫秒,其中秒和毫秒之间用逗号分隔。可以据此计算出开始的时间,以秒为单位。同理也能计算出结束时间。下标是9的是字符串“!!!”或者没有,前者表示该字幕是强制字幕,后者表示没有该属性。强制字幕就是即使你切换也会强制显示的字幕。下标10是我们的字幕。
最后调用下面的方法打包。
+ (instancetype)subtitleWithText:(NSString *)text timeRange:(CMTimeRange)timeRange forced:(BOOL)forced;
接下来是设置字幕显示标志,也就是是否强制,单独放出来,是因为要做整体性的判断。
// Set forced subtitles display flags as appropriate.
if ([mutableSubtitles count] == forcedCount)
{
for (Subtitle *subtitle in mutableSubtitles)
{
subtitle.displayFlags = kCMTextDisplayFlag_forcedSubtitlesPresent | kCMTextDisplayFlag_allSubtitlesForced;
}
}
else if (forcedCount > 0)
{
for (Subtitle *subtitle in mutableSubtitles)
{
subtitle.displayFlags = kCMTextDisplayFlag_forcedSubtitlesPresent;
}
}
_subtitles = [mutableSubtitles copy];
这边,如果每条字幕都是强制的,那么我们设置显示标记为kCMTextDisplayFlag_forcedSubtitlesPresent | kCMTextDisplayFlag_allSubtitlesForced,意思是所有字母都强制或者根据每条字幕自身属性而定。如果不是所有字幕都是强制的,那么设置为kCMTextDisplayFlag_forcedSubtitlesPresent,表明依据每条字幕的实际情况选择是否强制。现在我们的SubtitlesTextReader 已经拥有了一个字幕数组。