听闻少年二字,当与平庸相斥。这篇文章主要讲述快速剪辑-助力度咔智能剪辑提效实践相关的知识,希望能为你提供帮助。
文章图片
文章图片
一、设计背景度咔剪辑作为百度出品的一款泛知识类视频剪辑工具,承载着创作者的剪辑工作,而快速剪主要解决快速去除无效片段的问题。
文章图片
- 修剪片段
通常我们修剪片段时,需要人工跳转至大致位置,再通过播放视频来定位具体想要修剪的位置。快剪通过NLP识别视频中的字幕,按照视频timeline对齐展示,再通过字幕时长映射视频时间片段,快速修剪。
- 语气、停顿、重复句
由于泛知识类视频的特殊性,创作者录制过程中经常会出现语气、停顿、重复句等,假如我们需要修剪类似片段,只能够通过播放视频人工判定,操作繁琐。
快速剪辑基于视频中提取的音频,借助NLP将含有语气的片段标记出来,再通过字幕之间的时间间隙,直接标记出无声片段区。重复句则可依据计算相邻句式的字符重复度来标记。
文章图片
- Plugin:用于展示快剪控制器。
- Window:用于渲染与视频控制。
- Caption:用于字幕展示与操作。
文章图片
Window主要包含视频渲染、控制及撤销等功能:
- Timeline:存储视频的clip数据;TimelineModel:存储贴纸、字幕等数据信息。
- Streaming:负责渲染Timeline与TimelineModel数据信息,对视频进行预加载,播放控制,进度回调等。
- LiveWindow:用于展示Streaming渲染好的View,调节尺寸背景,坐标换算等。
- Restorer:用于保存用户操作,并对操作进行撤销、重做等。
文章图片
- 当进入快速剪辑页面时,会率先对是否已识别过字幕进行检测,若N,则提取视频音频并上传至后端,进行NLP分析,获取字幕和语气片段数据。
由于获取到字幕仅包含有效片段,得到后需要字幕与timeline做时间匹配。
【快速剪辑-助力度咔智能剪辑提效实践】(视频信息是由clip对象存储的,每个clip对象表示一个单独的视频片段。一个clip包含trimIn、trimOut、inPoint、outPoint,通过这些数据来确定一个视频片段展示哪个视频,展示在视频的什么位置。字幕同理,通过字幕的clip数据,把文字放在正确的位置上。)
文章图片
- 若已经有字幕文件,则对比距上次识别是否有增添视频,如果有新增,则把未进行字幕识别的视频进行音频提取,传至后端进行NLP分析与识别。
- 若对比后无新增视频,则直接进入空白句识别阶段。
- 以上文得到的字幕clip与视频的clip数据,对相邻字幕依据空白句规则增加空白字幕clip。
文章图片
- 目前快剪重复句以上下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 = https://www.songbingjia.com/android/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;
}
- 由于刷新timeline十分耗费性能,在删除字幕时,可只修改字幕clip数据源,刷新下方tableview展示,在用户退出时再刷新timeline,这样即可避免因实时刷新带来的性能问题。
- 一键优化后,将字幕数据回传于通用剪辑进行缓存及展示,也便于再次进入快速剪辑。
推荐阅读:
|基于etcd实现大规模服务治理应用实战
|短视频个性化Push工程精进之路
|百度爱番番数据分析体系的架构与实践
---------- END ----------
百度 Geek 说
百度官方技术公众号上线啦!
技术干货 · 行业资讯 · 线上沙龙 · 行业大会
招聘信息 · 内推信息 · 技术书籍 · 百度周边
欢迎各位同学关注
推荐阅读
- Flutter 专题05 图解修改应用名称及图标 #yyds干货盘点#
- 怎样阻止Linux服务器执行rm -rf /*命令
- #私藏项目实操分享# 使用脚本进行 SAP Spartacus 安装工作
- 怎样安装Arch Linux以及Deepin桌面环境
- 号外!号外!OHOS设备上的完整Python发布了!
- 怎样在Linux上安装Node.js
- #yyds干货盘点# IDEA 将普通 Java 项目打包成 Jar 包并运行
- vm 安装centos 7
- #IT人的升职加薪tips# 技术还是管理()