ノートの音声合成、動画生成までのVBAを作り、アドインとして使ってきました。
自分用としては申し分ありません。
ビデオでプレゼンを再現するのは、プレゼンの再利用にとって、とてもいいソリューションです。
しかし、他の人に使ってもらうには、アドインなどはちょっと敷居が高いですね
そこでC#で単体アプリを作りました
PPTXを読み込み、ノートを取得して、音声合成を行いWAVを追加したPPTXとビデオを新しく作成します。もとのPPTXはそのままです。
https://clinic.mond.jp/powerpoint/note2voice.zip
VBAからc#への変換ですが、なんと1日仕事でした。
細かいところでとても苦労しましたが、頭の体操にはとてもよかったです。
とにかくネットでは今は使われない古い情報と最新情報が混在していて、とても悩まれされました。
ネットの世界は常に進化しており、古い機能は無くなって、新しいものに変わっています。しかし、古い情報はネットに残り続けます。どれが正しいのかわからず困ります。
AIが自動判別してくれて、「これは古いから使えないよ」なんて言ってくれないかな
voicebox専用ですのでvoiceboxをインストールしてください
このアプリはvoiceboxと通信を行い、合成音声をvoiceboxから受け取ります。したがって、このアプリの起動前にvoiceboxを動かしてください
スピーカは
- 玄野武宏 ノーマル 11
- 青山龍星 ノーマル 13
- WhiteCUL ノーマル 23
- No.7 アナウンス 30
生成されたPPTXを開くと音声が挿入されています
mp4も作成されます
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Office.Interop.PowerPoint;
using Microsoft.Win32;
using System.IO;
using System.Runtime.InteropServices;
using System.Net.Http;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
using System.Media;
using System.Net.Http.Headers;
namespace note2voice
{
public partial class Form1 : Form
{
const string baseUrl = "http://127.0.0.1:50021/"; // localhostだとレスポンスが遅いのアドレス指定
private static readonly HttpClient httpClient = new HttpClient();
public Form1()
{
InitializeComponent();
ComboBox1.Items.Add(23);//波音リツ ノーマル
ComboBox1.Items.Add(11);//玄野武宏 ノーマル
ComboBox1.Items.Add(13);//青山龍星 ノーマル
ComboBox1.Items.Add(23);//WhiteCUL ノーマル
ComboBox1.Items.Add(30);//No7 アナウンス
}
private void Button1_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "Power Point file|*.pptx|Power Point file 2003|*.ppt|All files(*.*)|*.*";
ofd.Title = "Please select the Power point files.";
ofd.RestoreDirectory = true;
ofd.CheckFileExists = true;
ofd.CheckPathExists = true;
DialogResult rtn = ofd.ShowDialog();
if (rtn == DialogResult.OK)
{
label2.Text = ofd.FileName;
}
}
private void Button2_Click(object sender, EventArgs e)
{
label4.Text = "Start";
//テンプレートPPTファイル設定確認
String pptTempFilePath = label2.Text.ToString();
if (String.IsNullOrEmpty(pptTempFilePath) || pptTempFilePath.Equals("-"))
{
label4.Text = "テンプレートPowerPointファイルが設定されていません。";
return;
}
//PPT保存ファイル名を取得
String fileName = System.IO.Path.GetFileNameWithoutExtension(pptTempFilePath);
fileName = ReplaceStr(fileName);
//同一ファイル名がある場合は別名を作成
if (fileName == System.IO.Path.GetFileNameWithoutExtension(pptTempFilePath))
{
fileName = System.IO.Path.GetFileNameWithoutExtension(pptTempFilePath) + "_Voice";
}
//ファイル保存先のフルパスを作成
string pptGenerateFilePath = System.IO.Path.GetDirectoryName(pptTempFilePath) + "\\"
+ fileName
+ System.IO.Path.GetExtension(pptTempFilePath);
//置き換え文字辞書を作成
//
//PPTテンプレート置き換えを実施
DoReplace(pptTempFilePath, pptGenerateFilePath);
}
private async void DoReplace(string pptFilePath, string pptGenerateFilePath)
{
List<string> notes = new List<string>();
Microsoft.Office.Interop.PowerPoint.Application app = null;
Microsoft.Office.Interop.PowerPoint.Presentation ppt = null;
try
{
// PPTのインスタンス作成
app = new Microsoft.Office.Interop.PowerPoint.Application();
// PPTファイルオープン
ppt = app.Presentations.Open(
pptFilePath,
Microsoft.Office.Core.MsoTriState.msoTrue,
Microsoft.Office.Core.MsoTriState.msoTrue,
Microsoft.Office.Core.MsoTriState.msoFalse
);
// スライドのインデックスは1から 順にループする
for (int i = 1; i <= ppt.Slides.Count; i++)
{
//noteを取得する
Shape shp;
Slide sld;
sld = ppt.Slides[i];
shp = sld.NotesPage.Shapes.Placeholders[2];
String w = shp.TextFrame.TextRange.Text;
//add wav file
Slide oSlide = ppt.Slides[i];
await Createwav(w, oSlide);
//add wav file
String path = System.AppDomain.CurrentDomain.BaseDirectory + "temp.wav";
Shape oShp = oSlide.Shapes.AddMediaObject2(path, Microsoft.Office.Core.MsoTriState.msoFalse, Microsoft.Office.Core.MsoTriState.msoTrue);
oShp.AnimationSettings.PlaySettings.PlayOnEntry = Microsoft.Office.Core.MsoTriState.msoTrue;
oShp.AnimationSettings.PlaySettings.PlayOnEntry = Microsoft.Office.Core.MsoTriState.msoTrue;
oShp.AnimationSettings.PlaySettings.HideWhileNotPlaying = Microsoft.Office.Core.MsoTriState.msoFalse;
}
label4.Text = pptGenerateFilePath+"が生成されました";
ppt.CreateVideo(pptGenerateFilePath + ".mp4");
//生成PPTファイルの保存を実行
ppt.SaveAs(pptGenerateFilePath,
PpSaveAsFileType.ppSaveAsDefault,
Microsoft.Office.Core.MsoTriState.msoFalse);
while (ppt.CreateVideoStatus == PpMediaTaskStatus.ppMediaTaskStatusInProgress || ppt.CreateVideoStatus == PpMediaTaskStatus.ppMediaTaskStatusQueued)
{
System.Threading.Thread.Sleep(1500);
label4.Text = "ビデオ作成中";
}
label4.Text = "ビデオが作成されました";
}
finally
{
// PPTファイルを閉じる
if (ppt != null)
{
ppt.Close();
ppt = null;
}
// PPTインスタンスを閉じる
if (app != null)
{
app.Quit();
app = null;
}
}
}
private async Task Createwav(String note,Slide oSlide)
{
String w = ComboBox1.Text;
int vn = Int32.Parse(w);
String vpath = @"temp.wav";
// String note = @"おはようございます";
// VoicevoxUtility.Speek("これは直接再生するテストです", vn).Wait();
if (GenVoiceFlg.Checked==false) {
await Speek(note, vn);
}
await RecordSpeech(oSlide,vpath, note, vn);
}
private void Button3_Click(object sender, EventArgs e)
{
this.Close();
}
public static async Task Speek(string text, int speakerId)
{
string query = await CreateAudioQuery(text, speakerId);
// 音声合成
using var request = new HttpRequestMessage(new HttpMethod("POST"), $"{baseUrl}synthesis?speaker={speakerId}&enable_interrogative_upspeak=true");
request.Headers.TryAddWithoutValidation("accept", "audio/wav");
request.Content = new StringContent(query);
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
//var response = httpClient.SendAsync(request);
var response = await httpClient.SendAsync(request);
// 音声再生
using var httpStream = await response.Content.ReadAsStreamAsync();
var player = new SoundPlayer(httpStream);
player.PlaySync();
}
public static async Task RecordSpeech(Slide oSlide,string outputWaveFilePath, string text, int speaker)
{
string query = await CreateAudioQuery(text, speaker);
// 音声合成
using var request = new HttpRequestMessage(new HttpMethod("POST"), $"{baseUrl}synthesis?speaker={speaker}&enable_interrogative_upspeak=true");
request.Headers.TryAddWithoutValidation("accept", "audio/wav");
request.Content = new StringContent(query);
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
var response = await httpClient.SendAsync(request);
// 書き出し
using var fs = System.IO.File.Create(outputWaveFilePath);
using var stream = await response.Content.ReadAsStreamAsync();
stream.CopyTo(fs);
fs.Flush();
}
private static async Task<string> CreateAudioQuery(string text, int speakerId)
{
using var requestMessage = new HttpRequestMessage(new HttpMethod("POST"), $"{baseUrl}audio_query?text={text}&speaker={speakerId}");
requestMessage.Headers.TryAddWithoutValidation("accept", "application/json");
requestMessage.Content = new StringContent("");
requestMessage.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded");
var response = await httpClient.SendAsync(requestMessage);
return await response.Content.ReadAsStringAsync();
}
private String ReplaceStr(String targetStr)
{
foreach (string replaceKeyValKey in ReplaceKeyValDic.Keys)
{
//PPTテンプレートに「[置き換え対象文字列]」の書式で設定したものを変換
targetStr = targetStr.Replace("[" + replaceKeyValKey + "]", ReplaceKeyValDic[replaceKeyValKey]);
}
return targetStr;
}
//変換情報の辞書情報(再帰的に使用されるためGlobalで宣言)
Dictionary<String, String> ReplaceKeyValDic = new Dictionary<string, string>();
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
