パワーポイント ノートの音声合成アプリ作成
ノートの音声合成、動画生成までのVBAを作り、アドインとして使ってきました。自分用としては申し分ありません。ビデオでプレゼンを再現するのは、プレゼンの再利用にとって、とてもいいソリューションです。しかし、他の人に使ってもらうには、アドインなどはちょっと敷居が高いですねそこでC#で単体アプリを作りましたPPTXを読み込み、ノートを取得して、音声合成を行いWAVを追加したPPTXとビデオを新しく作成します。もとのPPTXはそのままです。https://clinic.mond.jp/powerpoint/note2voice.zipVBAからc#への変換ですが、なんと1日仕事でした。細かいところでとても苦労しましたが、頭の体操にはとてもよかったです。とにかくネットでは今は使われない古い情報と最新情報が混在していて、とても悩まれされました。ネットの世界は常に進化しており、古い機能は無くなって、新しいものに変わっています。しかし、古い情報はネットに残り続けます。どれが正しいのかわからず困ります。AIが自動判別してくれて、「これは古いから使えないよ」なんて言ってくれないかなvoicebox専用ですのでvoiceboxをインストールしてくださいこのアプリはvoiceboxと通信を行い、合成音声をvoiceboxから受け取ります。したがって、このアプリの起動前にvoiceboxを動かしてくださいスピーカは 玄野武宏 ノーマル 11 青山龍星 ノーマル 13 WhiteCUL ノーマル 23 No.7 アナウンス 30生成されたPPTXを開くと音声が挿入されていますmp4も作成されますVOICEVOX | 無料のテキスト読み上げ・歌声合成ソフトウェア無料で使える中品質なテキスト読み上げ・歌声合成ソフトウェア。商用・非商用問わず無料で、誰でも簡単にお使いいただけます。イントネーションを詳細に調整することも可能です。voicevox.hiroshiba.jpusing 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) { } } }