内容


利用 XML、PHP 和 Festival 制作 60 秒广播剧

使用计算机生成的声音制作最小的音频艺术

Comments

何谓 60 秒的戏剧?

就是用(大约)60 秒的时间来讲述故事。建立基本的情境,引入一个冲突,非常快速地描绘景物,最后以某种方式解决冲突,结局完美。您可以使用录制的声音,但是在本例中,我要使用利用 TTS 引擎 Festival 产生的合成声音。说出来的话会是平淡且不带感情的,并且 — 有时候 — 难以理解,因为合成的声音不是很完美。

下载 完整的样例剧本,即一个音频 .ogg 文件,然后继续往下阅读本文,了解剧本是如何生成的。在这里,剧本不重要,重要的是如何编程实现剧本。我完全利用开源的免费软件生成剧本,该软件使用的是 Festival TTS 引擎和它提供的一些有特色的声音。

播放剧本

播放带有两个文件的剧本:一个 XML 数据文件(包含剧本本身)和一个用 PHP 编写的制作器脚本。XML 数据包含演员表、字幕和剧终感谢、一系列用作效果的文件,以及每个人物的台词(对白)。根据 XML 数据文件中的指示(XML 文件使得创建不同的剧本或编辑当前剧本并用同一个制作器播放该剧本很容易),制作器将剧本呈现到音频设备。

剧本的基本结构

一个剧本有很多幕,每一幕又分成几场,每一场是一系列诸如噪音、音乐、对白和旁白之类的事件。在实际的剧场中,可以看到幕布在每一幕开始时被拉开,在结束时被合上,让置景工可以为剧本的下一幕改变背景。幕有助于将剧本分成几个部分,通常表示时间已过去或者到了另外一个地方。

在音频剧本中,没有幕布拉起时的那种生动的视觉效果。戏剧效果必须来自声音或者旁白。

您可以提供一种声音来表示幕布拉起和拉下,以提供一种可以听到的标志。此外,您还可以宣读剧本名称和剧作者。在每一幕的开始,一个人会说:“第一幕。在古罗马城镇广场的台阶上。” 在剧本的末尾,这个人会滚动剧中感谢并提供听众需要的解释(“Joe Blow 被判 70 年的牢狱……”)。有时,在剧本中间需要注解:比如说您听到一声耳光,并且此时只有两个演员在场,那么您有必要知道是谁打了谁。

剧中人物:演员表

演员表不要太长,否则每个人物在一分钟内说不上什么话。Festival 提供 9 种不同的声音 — 一些男声、一些女声、一些年老的声音、一些年轻的声音 — 所以我们将演员限制为 9 个。也许剧本中有一个角色叫 Fred,您想用 Festival 声音 voice_don_diphone 来代表 Fred(有关 Festival 声音的更多信息,请参见 参考资料)。可以将 Fred 声明为:

<role voice="voice_rab_diphone">Fred</role>

这里,角色是 Fred,声音是 XML 元素 role 的一个属性。每次在剧本中出现 Fred,制作器都知道使用什么声音来讲 Fred 的台词。如果您决定为 Fred 使用另外一种声音,只需要在一个地方更改属性即可。

分配旁白员

旁白员是一个特殊而重要的人物。此声音宣读剧本和作者的名称,注入解说词,以及列出剧终感谢。所以此声音将剧本紧紧绑定在一起。可以在演员表中将旁白员声明为:

<role voice="voice_don_diphone">Narrator</role>

现在,每次旁白员说话时,制作器都使用一种不同于 Fred 的声音。

音效和对白事件

剧本是一系列事件。随着每个事件的出现,环境要么变得更加清晰,要么变得更加复杂。这里的例子是两个连续事件 —— 一个人物的对白后面紧接着一种音效:

<event type="effect" player="mplayer">gunshot.wav</event>
<event type="dialogue" player="Bozo">Freeze, turkey</event>

第一个事件具有属性 effect,这表示它是一种声音,不用 TTS 引擎呈现。第二个属性表明您想要 Mplayer 来扮演此声音,即文件 gunshot.wav。

第二个事件是人物 Bozo 说的对白,他说:“别动,没用的东西。” 此环境中只有一个 TTS 引擎,所以不需要用属性来指定使用哪个引擎。制作器总是使用同一个引擎。

在此结构中,事件作为 XML 元素只出现在场中;但是,您也可以在幕和场结构外面具有声音和言语,比如旁白员的开场白、剧终感谢和欢呼。

幕和场

由于剧本包括很多幕,每一幕又分为几场,所以基本的流结构应该类似于 清单 1

清单 1. 幕、场和相关的事件
<act>
  <curtainUp>KDE_Window_Shade_Up.ogg</curtainUp>
  <scene>
    <event type="dialogue" player="five">...</event>
    <event type="dialogue" player="nine">...</event>
  </scene>
  <scene>
    <event type="dialogue" player="five">...</event>
    <event type="dialogue" player="two">...</event>
  </scene>
  <curtainDown>KDE_Window_Shade_Down.ogg</curtainDown>
</act>

这里,只有一幕,分为两场,每一场有两个人的台词,涉及到人物五、九和二 — 每个人物都有一种定义在演员表中的独特声音,前面已有解释。在幕的开始和结尾播放的文件指示了幕布的拉起和放下。在我的例子中,我借用了一些来自 K Desktop Environment (KDE) 的系统声音。

动态分配声音

让人物通过扩音器或耳麦说话的工作由制作器负责。制作器 PHP 代码包含 清单 2 中所示的函数。

清单 2. 动态地调用声音
function deliver($phrase,$voice) {
  exec('festival -b \'(begin ('.$voice.') 
         (SayText "'.$phrase.'"))\' >/dev/null',$out);
}

在此函数中,参数是短语(要说的内容)和声音(将用于呈现内容的 Festival 声音)。exec 函数以批模式调用 Festival 来做两件事:设置声音和使用指定的声音说出短语。begin 指示向 Festival 指出,这里有几件事情要做。

完整的剧本

清单 3 展示了一个完整的 XML 格式的简单剧本数据文件。

清单 3. XML 格式的完整剧本数据文件
<?xml version="1.0" encoding='UTF-8'?>
<play>
  <dramatisp>
    <role voice="voice_don_diphone">muchi</role>
    <role voice="voice_kal_diphone">dad</role>
    <role voice="voice_rab_diphone">narra</role>
    <role voice="voice_nitech_us_awb_arctic_hts">mscot</role>
    <role voice="voice_nitech_us_bdl_arctic_hts">spare</role>
    <role voice="voice_nitech_us_clb_arctic_hts">matron</role>
    <role voice="voice_nitech_us_jmk_arctic_hts">fuzzy</role>
    <role voice="voice_nitech_us_rms_arctic_hts">uncle</role>
    <role voice="voice_nitech_us_slt_arctic_hts">filly</role>
  </dramatisp>
  <intro>
    <music>chimes.ogg</music>
    <theatre>Sixty second theatre with XML and Festival</theatre>
    <title>Todays play - The demonstration effect</title>
  </intro>
  <act>
    <curtainUp>KDE_Window_Shade_Up.ogg</curtainUp>
    <scene>
      <!-- event type="effect" player="mplayer">tmp.wav</event -->
      <event type="dialogue" player="dad">The doctor is taking a long time</event>
      <event type="dialogue" player="matron">Yes but it is worth the wait</event>
      <event type="dialogue" player="dad">Looks like you broke your arm</event>
      <event type="dialogue" player="dad">Did you have a bad fall</event>
      <event type="dialogue" player="matron">Yes one of those silly falls</event>
      <event type="dialogue" player="matron">Icy steps</event>
      <event type="dialogue" player="dad">Could happen to anybody</event>
    </scene>
    <curtainDown>KDE_Window_Shade_Down.ogg</curtainDown>
  </act>
  <act>
    <curtainUp>KDE_Window_Shade_Up.ogg</curtainUp>
    <scene>
      <!-- event type="effect" player="mplayer">tmp.wav</event -->
      <event type="dialogue" player="dad">It is really cold out there</event>
      <event type="dialogue" player="uncle">Yes the cold gives me chill blains</event>
      <event type="dialogue" player="dad">Hands or feet</event>
      <event type="dialogue" player="uncle">Both</event>
      <event type="dialogue" player="dad">That is bad luck</event>
    </scene>
    <curtainDown>KDE_Window_Shade_Down.ogg</curtainDown>
  </act>
  <act>
    <curtainUp>KDE_Window_Shade_Up.ogg</curtainUp>
    <scene>
      <!--event type="effect" player="mplayer">tmp.wav</event -->
      <event type="dialogue" player="dad">Thats a bad cough</event>
      <event type="dialogue" player="filly">Yes it hurts when I breathe</event>
      <event type="dialogue" player="dad">I am sorry to hear that</event>
      <event type="dialogue" player="filly">What is your ailment</event>
      <event type="dialogue" player="dad">Oh I am not actually sick</event>
      <event type="dialogue" player="dad">But I do not feel well unless I surround
            myself with people who are a lot worse off</event>
    </scene>
    <curtainDown>KDE_Window_Shade_Down.ogg</curtainDown>
  </act>
  <end>
    <musicEnd></musicEnd>
    <credits>Thanks to Festival, PHP, Audacity and XML</credits>
  </end>
  <Applause></Applause>
</play>

在这个 XML 数据文件中,根元素是 <play>。除了数据文件中间的幕和场之外,剧本开头的元素 <dramatisp> 中声明了角色及其声音;在简介部分,旁白员或报幕员报出了剧本的名称;在结尾部分,有音乐、剧终感谢,也许还有欢呼。

制作器

除了一些次要的方面之外,现在已经具备了播放剧本所需的所有部件。制作器脚本在简介、幕、场和剧终之间迭代,在需要时,使用 Mplayer 或 Festival 按顺序播放事件。清单 4 展示了整个制作器脚本,它被编写为从命令行运行。

清单 4. 用 PHP 编写的制作器脚本
<?php
// sixty second theatre player
echo "60 second theater player\n";
if ($argc < 2) die("No play specified\n");
$playxml = $argv[1];
$xml = simplexml_load_file($playxml);
// load players' voices
$roles = $xml->dramatisp->role;
foreach ($roles as $rolevoice) {
  $rolev["$rolevoice"] = $rolevoice['voice'];
}
$announcer = $rolev["narra"];
$timestart = time();
//
// now the introduction
//
play_effect($xml->intro->music,"mplayer");
deliver((string) $xml->intro->theatre,$announcer);
deliver((string) $xml->intro->title,$announcer);
//
// now the acts
//
$anum = 0;
$snum = 0;
foreach ($xml->act as $A) {
  $anum++;
  deliver("Act $anum",$announcer);
  play_effect($A->curtainUp,"mplayer");
  foreach ($A->scene as $s) {
    $snum++;
    //deliver("Scene $snum",$announcer);
    play_effect("chimes1.ogg","mplayer");
    $events = $s->event;
    foreach ($events as $e) {
      switch ($e['type']) {
            case "effect":
              $engine = $e['player'];
              play_effect($e,$engine);
              break;
            case "dialogue":
              $plyr = $e['player'];
              // echo "Trying $e with $plyr\n";
              deliver($e,$rolev["$plyr"]);
              break;
            default:
              die("Invalid type");
              break;
      }
    }
  }
  play_effect($A->curtainDown,"mplayer");
  $snum = 0;
  }
//
// end of the play
//
deliver($xml->end->credits,$announcer);
$timeend = time();
$length = $timeend - $timestart;
echo("Total length is $length seconds.\n");
//
// functions
//
function play_effect($effect,$engine) {
exec("$engine $effect",$out);
}
function deliver($phrase,$voice) {
  // echo "$phrase with $voice\n";
  exec('festival -b \'(begin ('.$voice.') 
            (SayText "'.$phrase.'"))\' >/dev/null',$out);
}
?>

在这个制作器文件中,您首先将剧本(即 XML 数据文件 myplay.xml)加载到内存,并将它声明为一个 XML 对象。接下来,寻找演员表,并将它们加载到一个带有各自声音的数组中。然后,选择将用于旁白员或报幕员的声音,并记录开始时间,以便在剧本运行完时可以知道它运行了多长时间。在剧本名称宣布之后,马上进入幕的循环,在每一幕中,又是场的循环,场中包含事件指示。

第一次排演

要向扬声器播放剧本,启动制作器并提供数据文件:

$ php producer.php myplay.xml

通过让制作器将输出输送到一个录制的文件,您也可以记录剧本:

$ php producer.php myplay.xml | arecord (options) myplay.wav

然后您可以在一个诸如 Audacity®(参见 参考资料 中的链接)的音频编辑器中编辑此文件,或者用一个诸如 sox 的音频实用工具操纵它。

总结

尽管可以听录音带了,但是该过程的结果还是可以改善的。例如:

  • 让一些对话淡入和淡出或者重叠声音和对话,可能比较有帮助。尽管不能在剧本制作期间直接用制作器达到此目的,但是可以在后期利用诸如 Audacity 之类的工具做到。不过,越这样改善剧本,剧本就变得越复杂。
  • 对于缓慢的计算机来说,启动和停止音频引擎要花一两秒时间,因而会在输出的事件之间留下不必要的长长的静音。这些长长的静音在任何后期编辑中都是很有帮助的,因为它们清楚地显示了事件之间的分隔,但是对于最终的版本,应该缩短它们,以改善最终作品的间隔。这可以利用 Audacity 的 Truncate silence 效果做到。注意,使用这种不偏心的静音截去方法会删除您插入的任何电影剧本节拍(故意的静音),所以这样的节拍最好在截去静音之后再插入。
  • 使用您喜欢的任何音效和各种不同的声音,但是不要在如此短的时间内把事情搞得太复杂。使用诸如救护车汽笛或礼炮之类著名的音效是有帮助的,因为这些声音本身蕴含了环境信息。

结束语

您当然可以使用关系数据库而不是 XML 方法来存储剧本及其事件。但是在本例中,XML 对于读者和编辑来说更为清楚明了,因为它是平板文件形式。如果您不喜欢某句说出的对话台词,可以进行快速的编辑,重新运行制作器,得到新的最终作品。

带有平板而无感情的合成声音的 60 秒戏剧是一种最基本的艺术形式。没有训练有素的演员的表演,听众体会到的是一种别样的感觉。听众不得不自己去想象,补充一些细节,比如语气和语调。但是也完全可以使用 XML 和 Festival 或其他 TTS 引擎产生富有感情的作品。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Open source
ArticleID=502456
ArticleTitle=利用 XML、PHP 和 Festival 制作 60 秒广播剧
publish-date=07272010