导读:为了提升好看创作者剪辑效率,快速剪辑通过智能识别视频中的字幕、重复句、空白句,一键清除无效片段,提升剪辑效率。本文章旨在与大家分享快速剪辑的建设路径与实践过程中遇到的问题。
全文5886字,预计阅读时间15分钟。
度咔剪辑作为百度出品的一款泛知识类视频剪辑工具,承载着创作者的剪辑工作,而快速剪主要解决快速去除无效片段的问题。
目前快剪重复句以上下4句、长度大于3且须包含中文为判定条件,综合字符重复度、莱文斯坦距离、余弦相似性三种策略计算重复比率。
/**
* 字符重复度
*/
private func similarity(s1: String, s2: String) -> Float {
var simiCount: Float = 0
var string2Array = [String]()
for i in 0..<s2.count {
// 从任意位置开始截取到任意位置,闭区间
let string = subOneString(string: s2, from: i)
string2Array.append(string)
}
for i in 0..<s1.count {
let string1 = subOneString(string: s1, from: i)
if string2Array.contains(string1) {
let index2 = string2Array.firstIndex(of: string1)
string2Array.remove(at: index2!)
simiCount = simiCount + 1
}
}
if simiCount == 0 {
return 0.0
}
let rate: Float = simiCount / Float(max(s1.count, s2.count))
return rate
}
/**
* 莱文斯坦距离,是编辑距离的一种。指两个字串之间,由一个转成另一个所需的最少编辑操作次数。
*/
-(CGFloat)levenshteinDistance:(NSString *)s1 compare:(NSString *)s2 {
NSInteger n = s1.length;
NSInteger m = s2.length;
// 有一个字符串为空串
if (n * m == 0) {
return n + m;
}
// DP 数组
int D[n + 1][m + 1];
// 边界状态初始化
for (int i = 0; i < n + 1; i++) {
D[i][0] = i;
}
for (int j = 0; j < m + 1; j++) {
D[0][j] = j;
}
// 计算所有 DP 值
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < m + 1; j++) {
int left = D[i - 1][j] + 1;
int down = D[i][j - 1] + 1;
int left_down = D[i - 1][j - 1];
NSString *i1 = [s1 substringWithRange:NSMakeRange(i - 1, 1)];
NSString *j1 = [s2 substringWithRange:NSMakeRange(j - 1, 1)];
if ([i1 isEqualToString:j1] == NO) {
left_down += 1;
}
D[i][j] = MIN(left, MIN(down, left_down));
}
}
NSInteger maxLength = MAX(s1.length, s1.length);
CGFloat rate = 1.0 - ((CGFloat) D[n][m] / (CGFloat)maxLength);
returnrate;
}
/**
* 余弦相似性: 首先将字符串向量化,之后在一个平面空间中求出他们向量之间夹角的余弦值。
*/
-(CGFloat)cos:(NSString *)s1 compare:(NSString *)s2 {
NSMutableSet *setA = [NSMutableSet new];
for (int i = 0; i < [s1 length]; i++) {
NSString *string = [s1 substringWithRange:NSMakeRange(i, 1)];
[setA addObject:string];
}
NSMutableSet *setB = [NSMutableSet new];
for (int i = 0; i < [s2 length]; i++) {
NSString *string = [s2 substringWithRange:NSMakeRange(i, 1)];
[setB addObject:string];
}
// 统计字频
NSMutableDictionary *dicA = [NSMutableDictionary new];
NSMutableDictionary *dicB = [NSMutableDictionary new];
for (NSString *key in setA) {
NSNumber *value = dicA[key];
if (value == nil) {
value = @(0);
}
NSNumber *newValue = @([value integerValue] + 1);
dicA[key] = newValue;
}
for (NSString *key in setB) {
NSNumber *value = dicB[key];
if (value == nil) {
value = @(0);
}
NSNumber *newValue = @([value integerValue] + 1);
dicB[key] = newValue;
}
// 向量化,求并集
NSMutableSet *unionSet = [setA mutableCopy]; //取并集后
[unionSet unionSet:setB];
NSArray *unionArray = [unionSet allObjects];
NSMutableArray *aVec = [[NSMutableArray alloc] initWithCapacity:unionSet.count];
NSMutableArray *bVec = [[NSMutableArray alloc] initWithCapacity:unionSet.count];
for (NSInteger i = 0; i < unionArray.count; i++) {
[aVec addObject:@(0)];
[bVec addObject:@(0)];
}
for (NSInteger i = 0; i < unionArray.count; i++) {
NSString *object = unionArray[i];
NSNumber *numA = dicA[object];
if (numA == nil) {
numA = @(0);
}
NSNumber *numB = dicB[object];
if (numB == nil) {
numB = @(0);
}
aVec[i] = numA;
bVec[i] = numB;
}
// 分别计算三个参数
NSInteger p1 = 0;
for (NSInteger i = 0; i < aVec.count; i++) {
p1 += ([aVec[i] integerValue] * [bVec[i] integerValue]);
}
CGFloat p2 = 0.0f;
for (NSNumber *i in aVec) {
p2 += ([i integerValue] * [i integerValue]);
}
p2 = (CGFloat)sqrt(p2);
CGFloat p3 = 0.0f;
for (NSNumber *i in bVec) {
p3 += ([i integerValue] * [i integerValue]);
}
p3 = (CGFloat)sqrt(p3);
CGFloat rate = ((CGFloat) p1) / (p2 * p3);
return rate;
}