快速剪辑-助力度咔智能剪辑提效实践

听闻少年二字,当与平庸相斥。这篇文章主要讲述快速剪辑-助力度咔智能剪辑提效实践相关的知识,希望能为你提供帮助。

快速剪辑-助力度咔智能剪辑提效实践

文章图片

快速剪辑-助力度咔智能剪辑提效实践

文章图片

一、设计背景度咔剪辑作为百度出品的一款泛知识类视频剪辑工具,承载着创作者的剪辑工作,而快速剪主要解决快速去除无效片段的问题。
快速剪辑-助力度咔智能剪辑提效实践

文章图片

  • 修剪片段
    通常我们修剪片段时,需要人工跳转至大致位置,再通过播放视频来定位具体想要修剪的位置。快剪通过NLP识别视频中的字幕,按照视频timeline对齐展示,再通过字幕时长映射视频时间片段,快速修剪。
  • 语气、停顿、重复句
    由于泛知识类视频的特殊性,创作者录制过程中经常会出现语气、停顿、重复句等,假如我们需要修剪类似片段,只能够通过播放视频人工判定,操作繁琐。
    快速剪辑基于视频中提取的音频,借助NLP将含有语气的片段标记出来,再通过字幕之间的时间间隙,直接标记出无声片段区。重复句则可依据计算相邻句式的字符重复度来标记。
二、整体架构
快速剪辑-助力度咔智能剪辑提效实践

文章图片

  • Plugin:用于展示快剪控制器。
  • Window:用于渲染与视频控制。
  • Caption:用于字幕展示与操作。
2.1 Window
快速剪辑-助力度咔智能剪辑提效实践

文章图片

Window主要包含视频渲染、控制及撤销等功能:
  • Timeline:存储视频的clip数据;TimelineModel:存储贴纸、字幕等数据信息。
  • Streaming:负责渲染Timeline与TimelineModel数据信息,对视频进行预加载,播放控制,进度回调等。
  • LiveWindow:用于展示Streaming渲染好的View,调节尺寸背景,坐标换算等。
  • Restorer:用于保存用户操作,并对操作进行撤销、重做等。
2.2 CaptionCaption包含快剪的主要功能实现:字幕识别、字幕匹配、语气词、重复句等。
快速剪辑-助力度咔智能剪辑提效实践

文章图片

  • 当进入快速剪辑页面时,会率先对是否已识别过字幕进行检测,若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 说
百度官方技术公众号上线啦!
技术干货 · 行业资讯 · 线上沙龙 · 行业大会
招聘信息 · 内推信息 · 技术书籍 · 百度周边
欢迎各位同学关注

    推荐阅读