如何为“完全油门修复”还原视频。 第二部分

图片

在我以前的文章中,我谈到了如何从源FMV文件中提取内容,以及如何创建工具来分析大约67 GB的档案,以搜索用于创建FMV的中间组件。 这些零件是创建重新制作的FMV内容的基础,并被用作启动项目的“装配图”。

如前一篇文章所述,重制工作流分为三个分支:手绘框架的重制,3D模型的重制和声音重制。 下面,我将讨论工作流程的功能以及我们用于自动创建视频主要部分的技巧。

我们增加了所有原始手绘帧的大小,以匹配4K(3840x2160)的分辨率。 考虑到重做场景的宽度增加以及游戏以非正方形像素显示的事实,这意味着必须以4440x2400像素的分辨率创建所有重新制作的资源。

我们决定使用Adobe Animate来重新制作所有手绘的FMV帧,因为在开发“触手之日”之后,我们已经具有现成的工作流程。 艺术家团队很好地掌握了这一过程,因此我们没有考虑其他选择。


重新制作手绘框架的示例

归档中的原始3D模型位于3D Studio版本3中。幸运的是,现代版本的3D Studio Max能够使用另一个自动化脚本导入网格和电影关键帧的所有数据。 之后,我们将该中间文件转换为可在Autodesk Maya中使用的工作,艺术家在其中进行重新制作魔术。

为了给网格的表面赋予新的样式,应用了新的着色器,应用了高质量的纹理,并对这些网格进行了显着补充,以使模型具有更平滑的外观。 此外,由于原始摄像机是为较窄的宽高比而设计的,因此所有视频输入摄像机的帧窗口都已扩展为对应4440x2400像素的工作分辨率。


重新制作3D模型的示例

至于音频,我们设法找到了大多数原始的高质量版本,但是也有例外。 英语配音室的录音被打包到档案中,但是我们无法使用由外部合作伙伴参与的其他语言配音。 此外,我们设法找到了FMV中使用的The Gone Jackals的原始音乐。 某些版本的音效(SFX)已被具有类似声音类型的更“紧”的声音所代替。

以下是一个流程图,大致说明了我们如何处理源资源并将其链接到重新制作的内容。 原始提取的视频帧(使用SanExtract.exe)用作与所有存档数据文件进行比较的“源”。 归档清单文件是通过递归搜索所有归档数据来生成的; 它们用于快速查找特定类型的所有唯一文件。

SanWrangler工具用于直观地比较框架和存档数据的原始“来源”。 用户可以直观地将存档文件附加到原始框架,并将它们另存为XML格式的依赖关系图。 创建依赖关系图之后,使用Python脚本从原始“图形”文件资源以及Maya 3D的“装配图”中自动生成手动绘制的框架就足够了。 这些文件成为艺术家团队的起点,然后他们开始重新制作。


提取原始资源并创建“装配图”

这是导致我们获得现成的FMV重新制作版本的许多步骤中的第一步。 是的,当然,现在我们有了所有需要重做的文件的起点,但是如何将所有这些片段连接在一起?

下面,我将讨论FMV制造工作流程中使用的自动化方法。 这些方法不仅可以用于生成FMV,而且不仅可以应用于一种游戏; 我认为它们非常通用,可以在游戏开发的许多方面使用。

像大多数图形创建工作流程一样,此过程将是迭代的。 在源文件中的某个位置,可能存在美术人员需要修复的错误,有时必须重新导出与资源相关的文件。 我认为我们所有人都希望这项工作由计算机来完成,而不是由容易出错的人来完成。

我们完全知道“完全油门修复”视频的外观和声音,因此我们只需要改善其图形和声音即可。 所有视频都必须逐帧匹配原始视频,包括摄像机路径,音量,平移等。 为此,我们需要了解创建原始FMV的工作流程。 LucasArts档案中的这些67 GB数据包含许多有关原始内容工作方式的线索。 对于我们来说,这是一个伟大的开始。

创建原始FMV的过程


听起来可能有些怀旧,但我认为讨论游戏重新制作的“数字考古学”方面很重要。 最后,了解原始文档的创建过程将使您能够回答许多问题,并提供有关如何将资源转化为最终结果的线索。 而且,当创建新的翻新的FMV时,我们需要对原始的重新制作的资源应用相同的转换,以使最终产品看起来与原始产品尽可能接近。 包括我们需要以下内容:

  • 时间线上音轨的位置
  • 在游戏中播放时音轨的音量和声像设置
  • 帧组成和每个视频帧在成品中的位置

名为SMUSHFT(全油门的SMUSH)的工具允许FMV的创建者将视频和音频资源放置在时间轴上,然后对生成的FMV电影(.san格式)进行编码,然后由游戏引擎读取。 所有视频均分为一系列帧,这些帧粘合在一起以创建最终结果。 SMUSHFT允许用户沿时间轴直观地移动这些资源,并在必要时重做视频。

您不能提及我没有参与原始游戏的创建。 我只能猜测原始资源是如何创建的,研究归档数据并查看打包到该数据中的格式和可执行文件。 似乎3D模型是在Autodesk 3D Studio版本3中创建的,而手绘零件是在DeluxePaint Animation v1.0中创建的。 此外,我不知道音频的波形数据的生成是由哪个阶段组成的,但是每个使用的音频剪辑(.sad格式)都包含有关在游戏期间用于混合声音的音量和关键帧声像的信息。


创建原始FMV的过程

在创建框架的这些独立部分之后,执行了合并框架的过程。 此过程将3D帧渲染与手绘动画帧(以及其他所有内容)结合在一起,创建了SMUSHFT工具使用的成品(.nut文件)。 在项目准备好进行编码之后,将对视频进行处理,并且可以在游戏引擎中播放完成的结果(.san)。

SMUSHFT对原始视频(.san)的文件格式进行了最终编码,每个视频文件都有一个项目文件(.pro),该项目文件描述了视频的汇编(声音,视频,字幕位置)。 我们想要提取这些信息,以便我们可以生成Adobe Premiere Pro项目文件,并使用它来以4K分辨率对转换后的视频版本进行编码。 为此,我们需要对SMUSHFT项目文件进行反向工程。

逆向工程文件格式


拥有源代码很棒,因为您可以研究源代码并了解如何创建/读取项目文件。 如果没有源代码,则必须在十六进制编辑器中打开项目文件并分析文件中的模式。 这正是我们用来从SMUSHFT项目文件中提取有用内容的方式。

由于我们可以在DOSBox中运行原始的SMUSHFT,因此我们看到了该程序的用户界面,这为我们提供了有关文件格式的提示。 看一下打开原始.pro文件的以下屏幕截图:


SMUSHFT项目样本

在这里,您会注意到以下内容:文件中有命名资源(2027.NUT,2027.SAD,IN_06A.NUT等)。 这样的命名资源很可能在文件内部显示ASCII字符。 另外,在时间轴的顶部有帧计数器,在时间轴的左边有递增的层数。 最后一个-时间轴上的每个资源都位于特定的帧号上并具有一定的持续时间。 如果我们可以从原始项目文件中提取此信息,它将使我们知道在Adobe Premiere Pro时间轴上自动放置新资源的位置。


Adobe Premiere Pro示例项目

通过在十六进制编辑器中打开原始项目文件,您可以获得一些非常有用的信息。 看上面的例子用十六进制:


十六进制编辑器中的SMUSHFT项目文件

我们可以开始使用十六进制编辑器(我更喜欢Hexplorer)查看.pro文件,然后尝试查找模式。 您可以轻松地找到ASCII格式的命名资源,结尾处为零字节。 大约在同一存储区中,有一组存储为短裤(双字节整数)的值。 比较SMUSHFT中显示的数字和
项目文件中的十六进制数字为我们提供了将原始项目文件正确转换为现代视频编辑器(如Adobe Premiere Pro)的基础。

自动化工具包


此工作流程的大部分是自动化的,不需要人工干预。 原因之一是所有视频的内容都是完全从原始内容复制而来; 实际上,我们只是在升级内容。 因此,我们几乎没有机会完全更改FMV格式。 我们只需要找到一种方法即可使用高分辨率资源来重新创建视频,同时将花费在产品上的时间减至最少。

首先,我必须说,在使整个过程自动化之前,一个严肃的初始步骤应该是与一组内容创建者(图形和音频)进行对话。 原因是大多数自动化过程都要求创建者遵守关于准备项目,文件位置,使用的工具等的一组特定规则。 在我们的项目中,这意味着我们必须讨论用于创建手绘框架,3D模型和声音的内容的工具,然后是用于将所有这些组合在一起的视频编辑器。 还必须同意工作流的哪些部分将由手动执行,哪些将自动执行。

结果,我们决定了以下内容:

  • 手动绘制的帧将在Adobe Animate中创建,分辨率为4440x2400像素。
  • 3D模型和动画将在Autodesk Maya中创建并手动渲染,分辨率也为4440x2400像素
  • 音频文件将以48KHz和16位参数的.wav格式创建
  • 视频片段最初将自动生成,并且艺术家可以更改他需要的任何部分(有些例外)
  • 缝制和编码FMV的最后阶段将自动进行

为了使这些工具尽可能自动化,我们使用了几种方法。 选择Python作为将所有内容连接在一起的“胶水”,因为它可以被各种库很好地扩展,并且代码易于编写和维护。 我们还利用了其对平台无关的文件操作(复制,移动,删除)的内部支持。

Python-调用可执行文件,获取结果


Python子进程库对我们来说非常理想,因为它允许您更改其他可执行文件的执行,甚至可以等待其任务完成。 它允许您获取程序返回的代码并访问stdout和stderr缓冲区。

import subprocess # The command to execute command = 'SanExtract.exe -f -i credits.san -o \"C:/output_dir/\" ' # Execute the command via subprocess child = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # Wait for process to complete, returns stdout & stderr buffers stdout, stderr = child.communicate() # Retrieve the return code from the process return_code = child.returncode 

与Python中的可执行文件进行交互的示例

Python-Win32 API


Win32 API非常有用,因为它使我们能够通过脚本从Windows传输键盘和鼠标消息。 例如,您可以创建一个在屏幕的某些X,Y坐标上单击鼠标的函数:

 import win32api def ClickXY(x,y): win32api.SetCursorPos((x,y)) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,x,y,0,0) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,x,y,0,0) 

Python鼠标点击模拟示例

您甚至可以将击键事件发送到键盘(带有或不带有修饰符):

 import win32api import win32con def PressKey(code, modifierCode=None): if modifierCode: win32api.keybd_event(modifierCode, 0, 0, 0) win32api.keybd_event(code, 0, win32con.KEYEVENTF_EXTENDEDKEY | 0, 0) time.sleep(0.021) win32api.keybd_event(code, 0, win32con.KEYEVENTF_EXTENDEDKEY | win32con.KEYEVENTF_KEYUP, 0) if modifierCode: win32api.keybd_event(modifierCode, 0, win32con.KEYEVENTF_KEYUP, 0) 

Python键盘模拟范例

还有许多其他可能性,但是以上示例确实有助于实现我们的目标。 您可以将键盘事件发送到任何活动程序,它将开始键入它们,就像我们从键盘上输入内容一样,包括按热键。

Python-单击按钮的计算机视觉


最独特的体验是无法通过内部脚本自动执行的那些工具中使用计算机视觉软件。 大多数现代工具都具有脚本支持,但仍需要用户干预。 例如,3D Studio Max允许您从命令行运行MAXScript文件。 在本例中,我们运行脚本以自动导入3D网格文件,然后3D Studio Max将自动启动并显示“形状导入”对话框,用户必须在其中单击按钮:


对话框示例形状导入

所以-我们编写了一个自动化脚本,现在我们必须坐在屏幕前面,按下键? 不用坐在键盘上等待弹出窗口出现,我们可以使脚本截屏,使用与Python的OpenCV绑定来查找按钮图像模板并自动单击它。 这是上述示例的图像模板。


ok_button.png的图像模板

值得注意的是,图像模板包含其他功能(“单个对象”和“多个对象”的文本)。 这使我们可以获得更确定的搜索结果。 以下是一个示例Python脚本,用于自动单击图像模板的找到位置:

 import cv2 import ImageGrab # "Constants" TEMPLATE_THRESHOLD = 0.25 CLICK_OFFSET = 20 # Read the template image to search for template_image = cv2.imread('images/ok_button.png', 0) # Screenshot the current desktop and load it to a cv2 format screen = ImageGrab.grab() screen.save('screen.png') screen_image = cv2.imread('screen.png', 0) # Search for the template within the screenshot and retrieve search results match_result = cv2.matchTemplate(screen_image, template_image, cv2.TM_SQDIFF_NORMED) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(match_result) # If below the threshold, it's likely we know where to click if min_val < TEMPLATE_THRESHOLD: ClickXY(min_loc[0]+CLICK_OFFSET, min_loc[1]+CLICK_OFFSET) 

使用Python编写的使用OpenCV单击显示元素的示例

以上所有示例均基于Python。 但是有时候我们需要对Windows OS窗口系统进行更精确的控制。 这导致我们使用Windows Automation API开发本机工具。

Windows本机(C ++)-Windows自动化API


Windows Automation API提供对旧版Microsoft Active Accessibility API(MSAA)以及Microsoft UI Automation API的访问。 您可以在Microsoft页面上阅读有关此内容更多信息。

结果,我们能够向Windows界面的某些元素(按钮,文本字段,选项卡,菜单项)发出请求,找出这些元素在屏幕上的空间位置,然后单击/与它们交互。 Windows SDK还具有测试工具,可让您查看可用的属性。 他们使我们能够弄清楚在每个特定程序中可以自动执行什么操作。

Inspect.exe应用程序对于显示程序窗口管理的层次结构非常有用。 它提供了一个粗略的概念,例如菜单控件之类的对象的位置,以及如何使用自动化API调用来引用窗口元素。


示例Inspect.exe

学习完Windows程序的控件层次结构后,您将知道如何从主窗口的句柄中找到它,并学习如何通过API单击不同的元素:

 #include <WinUser.h> #include <UIAutomation.h> // Click on a sub-menu item given the Window & Menu handles. void ClickSubMenu(HWND hwnd, HMENU hmenu, const char *pMenuName) { // Iterate through the menu items of the window int menu_item_count = GetMenuItemCount(hmenu); for(int menu_id = 0; menu_id < menu_item_count; ++menu_id) { char menu_name[MAX_PATH]; int len = GetMenuString(hmenu, menu_id, reinterpret_cast<LPSTR>(&menu_name[0]), sizeof(menu_name), MF_BYPOSITION); // Look for the specific menu you're searching for and click it // Make sure to set the window active before doing it... if(!strcmp(pMenuName, menu_name)) { // now get the rect and click the center RECT rect; BOOL success = GetMenuItemRect(hwnd, hmenu, menu_id, &rect); if(success) { SetActiveWindow(hwnd); POINT point = GetMiddlePoint(rect); SetCursorPos(point.x, point.y); mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_LEFTDOWN, point.x, point.y, 0, 0); mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_LEFTUP, point.x, point.y, 0, 0); Sleep(DO_TASK_INTERVAL_WAIT_MS); } } } } 

在C ++中推送窗口控件的示例

当然,将击键传递到活动窗口也很简单:

 #include <WinUser.h> #include <UIAutomation.h> // Type the character string to the given window handle static void TypeCharacters(HWND window_handle, const char *pString) { int len = strlen(pString); for(int count = 0; count < len; ++count) { SendMessage(window_handle, WM_CHAR, (WPARAM)pString[count], (LPARAM)0); Sleep(CHARACTER_REPEAT_INTERVAL_MS); } } 

C ++键盘模拟示例

当然,这些API具有更多功能。 我发现,借助Inspect.exe工具,您可以了解可以访问程序窗口的哪些元素。

中间文字格式


我们工作流程的一部分是将文件保存为文本形式,并更改这些文本文件中的值。 最后,这些工具具有用于更改辅助数据状态的用户界面。 而且,如果您知道这些辅助数据应该是什么,则无需使用该工具,只需更改辅助数据即可。 诀窍是要知道如何处理这些支持数据。 在更改专有文件格式时,这可能是一个挑战。 如果每个人都有一个可以使用的简单文本文件,这会很好吗?

诀窍是找到一种解决大多数工具使用的专有文件格式的方法。 解决方案通常是使用大多数现代商业工具中的导入和导出选项。 以下是一些示例:

Adobe Premiere Pro以专有格式保存文件,但是您可以将项目导入或导出为Final Cut Pro XML。 导出为XML后,您可以按照需要的方式更改XML,然后将项目重新导入Adobe Premiere Pro。

另一个例子是对过时的Autodesk 3D Studio Release 3 3D网格格式中使用的纹理参考的校正,当导入原始网格文件时,我们使用ASCII字符将新转换的网格保存到中间的.fbx文件中。 以这种格式,您可以处理文本文件,并用正确的行替换所有纹理链接行。

Adobe Animate / Flash非常有趣,因为事实证明.fla文件实际上是“破碎的” .zip文件。 它们以未压缩的形式以XFL格式存储,该格式可以引用本地文件夹中的其他XFL对象(例如,位图)。 Double Fine首席工程师Oliver Franzke创建了一个经过修改的Python脚本,以使用ZIP打包/解压缩.fla文件,因此我们可以创建/修改这些文件。

使用范例


3D Studio Max


3D Studio Max的现代版本用于将原始.prj文件导入场景并以ASCII格式.fbx保存。 对于每个需要转换的.prj文件,都会从Python脚本中自动生成一个MaxScript文件(.ms),如下所示:

 importFile "G:\FullThrottle_Backup\FullThrottle_SourceAssets\BENBIKE.PRJ" #noPrompt 

使用MaxScript导入3D模型的示例

之后,此.ms文件仅被Python命令调用以在3dsmax.exe中运行:

 3dsmax.exe -U MAXScript "C:\FullThrottleRemastered\import_prj.ms" 

使用指定的MaxScript文件调用可执行文件的控制台命令示例

如上所述,在这种情况下,3D Studio Max打开了一个必须单击的对话框。 带有Python的OpenCV捆绑包帮助单击了此窗口中的按钮,从而可以在没有用户干预的情况下导入原始文件。 导入文件后,按下一系列菜单键(使用win32api Python)以启动另一个MAXScript文件,该文件以ASCII格式将模型导出为.fbx文件。 由于.fbx已保存为常规文本文件,因此模型纹理依赖关系的所有依赖关系都已替换为现代格式的图像链接。 然后,修改后的.fbx文件再次自动加载到3DSMax中,并作为.max文件导出。 此时,.max文件可以发送给艺术家以进行重新制作。

Adobe Animate / Flash


Adobe Animate / Flash用于重新掌握所有手绘的FMV资源。 我们采用了SanWrangler工具找到的原始手绘框架(尺寸为320x200像素),并将其用作“装配图”。 图像比例被放大以适合4440x2400像素,然后使用Python脚本自动生成.fla文件。

然后,利用我们对XFL格式Adobe Animate / Flash的了解,足以从头开始自动生成.fla文件。 我们能够使用Oliver Franzke已经创建的工具包来生成手绘动画文件的装配图。

Adobe Premiere Pro


Windows Automation API确实帮助我们确定了屏幕上显示的Premiere Pro控件。 在某些情况下,他们没有热键。 收到菜单项的坐标后,有必要将光标移动到这些坐标并发送鼠标单击事件。

所有这些都很棒,但是某些控件是以其他方式呈现的,因此对于Windows Automation API是不可见的。 对于这种情况,我们决定使用大量的OpenCV和Python,以便能够在脚本环境中使用OpenCV。 这在使用Adobe Premiere Pro时特别有用:尽管它部分支持JavaScript脚本,但所需的控件类型无法通过API获得。

此外,Adobe Premiere Pro项目文件以专有二进制格式存储。 因此,我们不仅可以神奇地创建Premiere Pro文件,还可以使用导入功能,该功能使我们可以将数据导入具有XML格式的Final Cut Pro文件中。 这样就足以生成正确的XML文件,在时间线上适当地放置所有资源,然后自动导入此.xml Final Cut Pro,以将其转换为所需的格式。 然后,我们可以将导出的帧放在自动队列中,以便可以将它们组合成完整的视频。

所有阶段


下面是一个通用框图,显示了新工作流程的所有自动化部分。 每个自动化段都由一个圆角矩形围绕,并带有有关所用自动化技术的其他信息。


简化的重新制作的FMV自动化流程图

您会注意到,使用Adobe Premiere Pro进行的大多数工作都需要使用Python以及专用的本机Windows代码。 原因是Premiere Pro窗口的结构复杂,以及需要使用本机Windows Automation API来确保与该应用程序的所有从属子窗口正确交互。

一起


使用上述方法,我们能够配置多台自动化机器,以将所有视频的工作分成多个部分。 此外,Slack Bot已集成到工作流中,以根据通过处理管道的视频状态将有关自动化的反馈发送到我们的Slack频道,以便我们知道何时出现问题。


Adobe Premiere Pro自动化示例

我们面临的问题


所有这些听起来不错,但实际上,在实施项目时,我们遇到了问题。 我将仅列出要点。

1)混合完成的音频的迭代。 逐步重新制作所有音频文件。 因此,例如,当我们有声音效果“ BOOM!”时,声音工程师不知道将其插入混音中的位置,因此他不得不等待视频编码才能找出问题所在。

2)存储未压缩的中间文件。 帧以未压缩的格式存储,直到编码到最终视频的最后一刻为止。 因此,有必要在本地存储中存储大量帧,其中一些存储在版本控制系统中。 这种存储量的增加非常明显,并且在使用某些版本控制系统(我们使用Perforce)时可能会非常昂贵。

3)交货时间。 工作流程的重要部分是自动化的,这使工程师可以做其他事情。 但是,创建一个视频所需的时间可能会很长。 最耗时的部分是以4k分辨率编码帧。 我们有调查Perforce内部资源状态的方法,以了解需要重新执行哪些步骤,但是这种方法并没有像我们所希望的那样分成几部分。

后续步骤


是的,文章篇幅如此之大! 尽管我们对此项目的工作流程实施非常具体,但我相信某些自动化方法可以用于开发任何游戏。 看完视频后,您可以考虑一个相关主题-在游戏执行期间播放FMV。 这包括诸如编码多语言音频流以及播放原始FMV时的帧同步之类的问题。 等待文章的第三部分!

Source: https://habr.com/ru/post/zh-CN422367/


All Articles