本文告诉大家如何从 PPTX 文件里面解析出视频
我期望看到本文的小伙伴是了解 OpenXML 的,如果想要解析 Office 的文档,我推荐使用使用 OpenXML SDK 这个开源的库,更多入门级博客请看 C# dotnet 使用 OpenXml 解析 PPT 文件
我做了一个简单的 PPT 文件,这个文件里面只有一页,这一页上面有一个视频。做这个文件的作用是方便调试,本文将从这个文件里面拿到视频
using (var presentationDocument =
DocumentFormat.OpenXml.Packaging.PresentationDocument.Open(@"小视频.pptx", false))
{
var presentationPart = presentationDocument.PresentationPart;
var slidePart = presentationPart.SlideParts.FirstOrDefault();
// 忽略代码
}
上面代码是打开解析文件,我拿到第一页,而获取页面的元素需要了解一点是 PPT 将所有元素存放 ShapeTree 里面。相当于所有的元素都放在形状树里面。在 PPT 里面是不存在视频元素的,没有一个叫做视频的元素,在 PPT 里面用 Picture 存放视频。也就是说视频也是一张图片,图片也是一张图片。不过视频的图片采用的是视频的缩略图。在解析的时候,需要先尝试当成视频解析,视频解析失败了,再当成图片解析
在 OpenXML 的存储的视频大概如下
<p:pic>
<p:nvpicpr>
<p:cnvpr id="4" name="视频">
<a:hlinkclick action="ppaction://media" r:id="">
</a:hlinkclick>
</p:cnvpr>
<p:cnvpicpr>
<a:piclocks nochangeaspect="1">
</a:piclocks>
</p:cnvpicpr>
<p:nvpr>
<a:videofile r:link="rId2">
</a:videofile>
<p:extlst>
<p:ext uri="{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}">
<p14:media r:embed="rId1" xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main">
</p14:media>
</p:ext>
</p:extlst>
</p:nvpr>
</p:nvpicpr>
</p:pic>
如上面代码,这就是视频元素其实也就是 Picture 元素,可以在 picture.NonVisualPictureProperties.ApplicationNonVisualDrawingProperties
属性里面找到 VideoFromFile 对象,通过此对象获取到视频内容,从而解析视频
var picture = slidePart.Slide.CommonSlideData.ShapeTree.OfType<Picture>().FirstOrDefault();
var videoFromFile = picture.NonVisualPictureProperties
.ApplicationNonVisualDrawingProperties
.GetFirstChild<VideoFromFile>();
如上面的 OpenXML 代码,可以看到视频是通过 r:link
关联对应的资源。获取视频内容的方法用的是 r:link
拿到对应的资源。在 OpenXmlSDK 里面,获取 Relationship 有两个不同的方式,用 GetPartById 获取 ChildrenRelationshipParts 的资源,用 GetReferenceRelationship 拿到 ReferenceRelationshipList 的资源。由于在 PPT 里面的视频放在 ReferenceRelationshipList 里,可以使用使用下面代码拿到
var openXmlPart = (DataPartReferenceRelationship) slidePart.GetReferenceRelationship(videoFromFile.Link.Value);
通过 GetStream 方法可以拿到压缩包里面的文件,这里不需要解压缩,在底层已自动解压缩内容,新建文件写入就可以
var stream = openXmlPart.DataPart.GetStream();
var file = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "林德熙是逗比.mp4");
File.WriteAllBytes(file, ReadAllBytes(stream));
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 8551ad78455f7e56e2f1cafa66d6ae62d7a94995
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
获取代码之后,进入 GairhajelkewaiHeyerjeaginu 文件夹
以上是大部分的 PPT 存放视频的存储方式,通过以上方法可以获取大多数课件里的视频
然而还有一部分比较特殊的,是需要通过 DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230
扩展获取。在上面的 OpenXML 存储代码里面,在 a:videofile
下面还有一段 p:extlst
的代码。这里的 p:extlst
就是 DocumentFormat.OpenXml.Presentation.ApplicationNonVisualDrawingPropertiesExtensionList
类型,表示的是存储扩展类型,存储扩展里面,可以通过 Uri 指定此扩展的 Guid 从而指定这是一个什么扩展
<p:nvpr>
<a:videofile r:link="rId2">
</a:videofile>
<p:extlst>
<p:ext uri="{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}">
<p14:media r:embed="rId1" xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main">
</p14:media>
</p:ext>
</p:extlst>
</p:nvpr>
以上的给视频元素使用的 DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230
是一段特殊的扩展,叫做 media extension
媒体扩展,可以通过 ECMA 376 的第 2.3.1.18 章了解相关的信息
在 DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230
扩展里面存放的多媒体,可以附加储存以下功能
- 进行软裁剪,设置视频的循环播放周期,设置起始点和结束点,让视频在起始点和结束点中间播放
- 提供设置视频的淡入功能
- 允许在视频中设置书签,以便快速跳转到特定位置
本文这里只告诉大家如何获取到视频文件
对于某些特殊的文件来说,在 <a:videoFile r:link="rId1" />
记录的内容也许只是一个空对象而已。如对应的 rId1
的内容如下
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/video" Target="NULL" TargetMode="External" />
可以看到这里只是连接到一个 Target="NULL"
的外部内容,也就是一个空对象。如此是不能通过上文的方式获取到视频文件的,需要通过以下的方式获取
先获取到扩展列表 DocumentFormat.OpenXml.Presentation.ApplicationNonVisualDrawingPropertiesExtensionList
对象,接着根据 DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230
获取到具体的扩展
// 这里的 ApplicationNonVisualDrawingProperties 包含以下内容
/*
<p:nvPr>
<a:videoFile r:link="rId1" />
<p:extLst>
<p:ext uri="{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}">
<p14:media xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" r:embed="rId2">
<p14:trim st="109898" end="23655" />
</p14:media>
</p:ext>
</p:extLst>
</p:nvPr>
*/
// 获取 <p:extLst> 的内容
var extensionList = applicationNonVisualDrawingProperties!.GetFirstChild<DocumentFormat.OpenXml.Presentation.ApplicationNonVisualDrawingPropertiesExtensionList>();
// 获取特殊的多媒体扩展
/*
Extension DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230 is described as a media extension. It appears to allow:
- “cropping” the video period (set start and stop time markers)
- provide for “fade-in”
- allow for setting bookmarks in the video for fast jumps to a particular location
*/
var mediaExtension = extensionList?.Elements<DocumentFormat.OpenXml.Presentation.ApplicationNonVisualDrawingPropertiesExtension>().FirstOrDefault(extension => extension.Uri?.Value == "{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}");
通过媒体扩展获取到 DocumentFormat.OpenXml.Office2010.PowerPoint.Media
也就是 p14:media
的内容
if (mediaExtension != null)
{
// p14:media
var media = mediaExtension.GetFirstChild<DocumentFormat.OpenXml.Office2010.PowerPoint.Media>();
}
接着如上文的方法,通过 media.Embed
获取到对应的资源,代码如下
var currentPart = slidePart;
var media = mediaExtension.GetFirstChild<DocumentFormat.OpenXml.Office2010.PowerPoint.Media>();
var embedValue = media?.Embed?.Value;
if (embedValue != null)
{
var videoPart = currentPart.GetReferenceRelationship(embedValue);
return videoPart as DataPartReferenceRelationship;
}
获取到 videoPart
即可使用上面的代码的方式保存为本地文件
本文会经常更新,请阅读原文: https://blog.lindexi.com/post/C-dotnet-%E4%BD%BF%E7%94%A8-OpenXml-%E8%A7%A3%E6%9E%90-PPT-%E9%87%8C%E9%9D%A2%E7%9A%84%E8%A7%86%E9%A2%91.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。
无盈利,不卖课,做纯粹的技术博客
以下是广告时间
推荐关注 Edi.Wang 的公众号
欢迎进入 Eleven 老师组建的 .NET 社区
以上广告全是友情推广,无盈利