Skip to content

Commit

Permalink
Merge pull request #31 from OUCC/feat/#1-4
Browse files Browse the repository at this point in the history
#1-4 生成タスク周りをリファクタリング
  • Loading branch information
aiueo-1234 authored Apr 5, 2024
2 parents 2dd3ea7 + 6e861c9 commit 6dd9ce6
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 105 deletions.
2 changes: 1 addition & 1 deletion Epub/KoeBook.Epub/Models/Paragraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ public sealed class Paragraph : Element
{
public ScriptLine? ScriptLine { get; set; }
public Audio? Audio => ScriptLine?.Audio;
public string? Text { get; set; }
public string Text { get; set; } = "";
}
78 changes: 21 additions & 57 deletions Epub/KoeBook.Epub/Services/AnalyzerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ public partial class AnalyzerService(IScraperSelectorService scrapingService, IE
private readonly IScraperSelectorService _scrapingService = scrapingService;
private readonly IEpubDocumentStoreService _epubDocumentStoreService = epubDocumentStoreService;
private readonly ILlmAnalyzerService _llmAnalyzerService = llmAnalyzerService;
private Dictionary<string, string> _rubyReplacements = new Dictionary<string, string>();

public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties, string tempDirectory, string coverFilePath, CancellationToken cancellationToken)
public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties, string tempDirectory, CancellationToken cancellationToken)
{
coverFilePath = Path.Combine(tempDirectory, "Cover.png");
Directory.CreateDirectory(tempDirectory);
var coverFilePath = Path.Combine(tempDirectory, "Cover.png");
using var fs = File.Create(coverFilePath);
await fs.WriteAsync(CoverFile.ToArray(), cancellationToken);
await fs.FlushAsync(cancellationToken);
Expand All @@ -27,43 +27,27 @@ public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties,
{
document = await _scrapingService.ScrapingAsync(bookProperties.Source, coverFilePath, tempDirectory, bookProperties.Id, cancellationToken);
}
catch (EbookException)
{
throw;
}
catch (Exception ex)
{
EbookException.Throw(ExceptionType.WebScrapingFailed, "", ex);
EbookException.Throw(ExceptionType.WebScrapingFailed, innerException: ex);
return default;
}
_epubDocumentStoreService.Register(document, cancellationToken);

var scriptLines = new List<ScriptLine>();
foreach (var chapter in document.Chapters)
{
foreach (var section in chapter.Sections)
var scriptLines = document.Chapters.SelectMany(c => c.Sections)
.SelectMany(s => s.Elements)
.OfType<Paragraph>()
.Select(p =>
{
foreach (var element in section.Elements)
{
if (element is Paragraph paragraph)
{
var line = paragraph.Text;
// rubyタグがあればルビのdictionaryに登録
var rubyDict = ExtractRuby(line);
// ルビを置換
var line = ReplaceBaseTextWithRuby(p.Text);
foreach (var ruby in rubyDict)
{
if (!_rubyReplacements.ContainsKey(ruby.Key))
{
_rubyReplacements.Add(ruby.Key, ruby.Value);
}
}
// ルビを置換
line = ReplaceBaseTextWithRuby(line, rubyDict);

var scriptLine = new ScriptLine(line, "", "");
paragraph.ScriptLine = scriptLine;
scriptLines.Add(scriptLine);
}
}
}
}
return p.ScriptLine = new ScriptLine(line, "", "");
}).ToList();

// 800文字以上になったら1チャンクに分ける
var chunks = new List<string>();
Expand All @@ -85,32 +69,12 @@ public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties,
return bookScripts;
}

private static Dictionary<string, string> ExtractRuby(string text)
{
var rubyDict = new Dictionary<string, string>();
var rubyRegex = new Regex("<ruby><rb>(.*?)</rb><rp>(</rp><rt>(.*?)</rt><rp>)</rp></ruby>");

foreach (Match match in rubyRegex.Matches(text))
{
if (!rubyDict.ContainsKey(match.Groups[1].Value))
{
rubyDict.Add(match.Groups[1].Value, match.Groups[2].Value);
}
}

return rubyDict;
}

private static string ReplaceBaseTextWithRuby(string text, Dictionary<string, string> rubyDict)
private static string ReplaceBaseTextWithRuby(string text)
{
// 元のテキストからルビタグをすべてルビテキストに置き換える
var resultText = text;
foreach (var pair in rubyDict)
{
var rubyTag = $"<ruby><rb>{pair.Key}</rb><rp>(</rp><rt>{pair.Value}</rt><rp>)</rp></ruby>";
resultText = resultText.Replace(rubyTag, pair.Value);
}

return resultText;
return RubyRegex().Replace(text, m => m.Groups[2].Value);
}

[GeneratedRegex(@"<ruby>\s*<rb>(.*?)</rb>\s*<rp>\s*[(《\(]\s*</rp>\s*<rt>(.*?)</rt>\s*<rp>\s*[)》\)]\s*</rp>\s*</ruby>", RegexOptions.Multiline)]
private static partial Regex RubyRegex();
}
4 changes: 1 addition & 3 deletions Epub/KoeBook.Epub/Services/EpubGenerateService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using KoeBook.Core;
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Models;
using KoeBook.Epub;
using KoeBook.Epub.Contracts.Services;
using KoeBook.Epub.Models;

Expand All @@ -17,8 +16,7 @@ public async ValueTask<string> GenerateEpubAsync(BookScripts bookScripts, string
{
cancellationToken.ThrowIfCancellationRequested();

var document = _documentStoreService.Documents.Where(doc => doc.Id == bookScripts.BookProperties.Id).FirstOrDefault()
?? throw new InvalidOperationException($"The epub document ({bookScripts.BookProperties.Id}) can't be found.");
var document = _documentStoreService.Documents.Single(d => d.Id == bookScripts.BookProperties.Id);

foreach (var scriptLine in bookScripts.ScriptLines)
{
Expand Down
2 changes: 1 addition & 1 deletion KoeBook.Core/Contracts/Services/IAnalyzerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ public interface IAnalyzerService
/// 本の情報の取得・解析を行います
/// </summary>
/// <returns>編集前の読み上げテキスト</returns>
ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties, string tempDirectory, string coverFilePath, CancellationToken cancellationToken);
ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties, string tempDirectory, CancellationToken cancellationToken);
}
2 changes: 2 additions & 0 deletions KoeBook.Core/Contracts/Services/IDisplayStateChangeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public interface IDisplayStateChangeService
/// </summary>
void UpdateState(BookProperties bookProperties, GenerationState state);

void UpdateTitle(BookProperties bookProperties, string title);

/// <summary>
/// プログレスバーを更新します
/// </summary>
Expand Down
16 changes: 15 additions & 1 deletion KoeBook.Core/Helpers/IDisplayStateChangeEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,23 @@ public class DisplayStateChanging(IDisplayStateChangeService displayStateChangeS

private readonly int _maximum = maximum;

private int _progress;

public void UpdateProgress(int progress)
{
_displayStateChangeService.UpdateProgress(_bookProperties, progress, _maximum);
_displayStateChangeService.UpdateProgress(_bookProperties, _progress = progress, _maximum);
}

public void IncrementProgress()
{
_progress++;
_displayStateChangeService.UpdateProgress(_bookProperties, _progress, _maximum);
}

public void Finish()
{
_progress = _maximum;
_displayStateChangeService.UpdateProgress(_bookProperties, _progress, _maximum);
}
}
}
33 changes: 33 additions & 0 deletions KoeBook.Test/Epub/AnalyzerServiceTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Runtime.CompilerServices;
using KoeBook.Epub.Services;

namespace KoeBook.Test.Epub;

public class AnalyzerServiceTest
{
[Theory]
[InlineData("aa", "aa")]
[InlineData("<ruby><rb>漢字</rb><rp>(</rp><rt>かんじ</rt><rp>)</rp></ruby>", "かんじ")]
[InlineData("ああ<ruby><rb>漢字</rb><rp>(</rp><rt>かんじ</rt><rp>)</rp></ruby>あああ", "ああかんじあああ")]
[InlineData("""
ああ<ruby><rb>漢字</rb><rp>(</rp><rt>かんじ</rt><rp>)</rp></ruby>あああ
ああ<ruby><rb>漢字</rb><rp>(</rp><rt>かんじ</rt><rp>)</rp></ruby>あああ
ああ<ruby><rb>漢字1</rb><rp>(</rp><rt>かんじ1</rt><rp>)</rp></ruby>あああ
""", "ああかんじあああ\nああかんじあああ\nああかんじ1あああ")]
[InlineData("<ruby> <rb>佐久平</rb> <rp>\n《 </rp> <rt>さくだいら</rt> <rp>》</rp> </ruby> <ruby><rb>啓介</rb><rp>《</rp><rt>けいすけ</rt><rp>》</rp></ruby>",
"さくだいら けいすけ")]
[InlineData("<ruby><rb>漢字</rb>\n<rp>(</rp><rt>かんじ</rt><rp>)</rp></ruby>", "かんじ")]
[InlineData("ああ<ruby><rb>漢字</rb><rp>(</rp><rt>かんじ</rt><rp>)</rp></ruby>あああ<ruby><rb>漢字</rb><rp>(</rp><rt>カンジ</rt><rp>)</rp></ruby>", "ああかんじあああカンジ")]
public void ReplaceBaseTextWithRuby(string input, string expected)
{
var result = AnalyzerServiceProxy.ReplaceBaseTextWithRuby(null, input);

Assert.Equal(expected, result);
}
}

file static class AnalyzerServiceProxy
{
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod)]
public static extern string ReplaceBaseTextWithRuby(AnalyzerService? _, string text);
}
4 changes: 2 additions & 2 deletions KoeBook.Test/Epub/EpubDocumentTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public void EnsureParagraph()
var element = Assert.Single(section.Elements);
var paragraph = Assert.IsType<Paragraph>(element);
Assert.Null(paragraph.Audio);
Assert.Null(paragraph.Text);
Assert.Empty(paragraph.Text);
Assert.Null(paragraph.ClassName);

// 空でないときは無視
Expand Down Expand Up @@ -129,7 +129,7 @@ public void EnsureParagraph()
element = Assert.Single(document.Chapters[0].Sections[1].Elements);
paragraph = Assert.IsType<Paragraph>(element);
Assert.Null(paragraph.Audio);
Assert.Null(paragraph.Text);
Assert.Empty(paragraph.Text);
Assert.Null(paragraph.ClassName);

// インデックスは正しく指定する必要がある
Expand Down
4 changes: 1 addition & 3 deletions KoeBook/Services/CoreMocks/AnalyzerServiceMock.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Helpers;
using KoeBook.Core.Models;
using KoeBook.Epub;
using KoeBook.Epub.Models;
using static KoeBook.Core.Helpers.IDisplayStateChangeEx;

namespace KoeBook.Services.CoreMocks;
Expand All @@ -11,7 +9,7 @@ public class AnalyzerServiceMock(IDisplayStateChangeService stateService) : IAna
{
private readonly IDisplayStateChangeService _stateService = stateService;

public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties, string tempDirectory, string coverFilePath, CancellationToken cancellationToken)
public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties, string tempDirectory, CancellationToken cancellationToken)
{
DisplayStateChanging stateChanging;
if (bookProperties.SourceType == SourceType.Url)
Expand Down
9 changes: 9 additions & 0 deletions KoeBook/Services/DisplayStateChangeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,13 @@ public void UpdateState(BookProperties bookProperties, GenerationState state)
taskService.GetProcessingTask(bookProperties.Id).State = state;
});
}

public void UpdateTitle(BookProperties bookProperties, string title)
{
var taskService = _taskService; // thisをキャプチャしないようにする
_ = App.MainWindow.DispatcherQueue.TryEnqueue(() =>
{
taskService.GetProcessingTask(bookProperties.Id).Title = title;
});
}
}
70 changes: 33 additions & 37 deletions KoeBook/Services/GenerationTaskRunnerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,50 +48,45 @@ private async void TasksChanged(GenerationTask task, ChangedEvents changedEvents

private async ValueTask RunAsync(GenerationTask task)
{
try
{
var scripts = await _analyzerService.AnalyzeAsync(new(task.Id, task.Source, task.SourceType), _tempFolder, "", task.CancellationToken);
task.BookScripts = scripts;
task.State = GenerationState.Editting;
task.Progress = 0;
task.MaximumProgress = 0;
if (task.SkipEdit)
{
var resultPath = await _epubGenService.GenerateEpubAsync(scripts, _tempFolder, task.CancellationToken);
task.State = GenerationState.Completed;
task.Progress = 1;
task.MaximumProgress = 1;
var fileName = Path.GetFileName(resultPath);
File.Copy(resultPath, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "KoeBook", fileName), true);
}
}
catch (OperationCanceledException)
{
task.State = GenerationState.Failed;
}
catch (EbookException e)
{
task.State = GenerationState.Failed;
await _dialogService.ShowInfoAsync("生成失敗", e.ExceptionType.GetEnumMemberValue()!, "OK", default);
}
catch
{
task.State = GenerationState.Failed;
}
if (task.CancellationToken.IsCancellationRequested || task.State == GenerationState.Failed)
return;

await RunAsyncCore(task, true);
await RunAsyncCore(task, false);
}

public async void RunGenerateEpubAsync(GenerationTask task)
{
if (task.CancellationToken.IsCancellationRequested || task.State == GenerationState.Failed || task.BookScripts is null)
return;

await RunAsyncCore(task, false);
}

private async ValueTask RunAsyncCore(GenerationTask task, bool firstStep)
{
var tempDirectory = Path.Combine(_tempFolder, task.Id.ToString());
try
{
var resultPath = await _epubGenService.GenerateEpubAsync(task.BookScripts, _tempFolder, task.CancellationToken);
task.State = GenerationState.Completed;
task.Progress = 1;
task.MaximumProgress = 1;
var fileName = Path.GetFileName(resultPath);
File.Copy(resultPath, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "KoeBook", fileName), true);
if (firstStep)
{
var scripts = await _analyzerService.AnalyzeAsync(new(task.Id, task.Source, task.SourceType), tempDirectory, task.CancellationToken);
task.BookScripts = scripts;
task.State = GenerationState.Editting;
task.Progress = 0;
task.MaximumProgress = 0;
}
else if (task.BookScripts is not null)
{
var resultPath = await _epubGenService.GenerateEpubAsync(task.BookScripts, tempDirectory, task.CancellationToken);
task.State = GenerationState.Completed;
task.Progress = 1;
task.MaximumProgress = 1;
var fileName = Path.GetFileName(resultPath);
File.Move(resultPath, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "KoeBook", fileName), true);
}
else
throw new InvalidOperationException();
}
catch (OperationCanceledException)
{
Expand All @@ -102,9 +97,10 @@ public async void RunGenerateEpubAsync(GenerationTask task)
task.State = GenerationState.Failed;
await _dialogService.ShowInfoAsync("生成失敗", e.ExceptionType.GetEnumMemberValue()!, "OK", default);
}
catch
catch (Exception e)
{
task.State = GenerationState.Failed;
await _dialogService.ShowInfoAsync("生成失敗", $"不明なエラーが発生しました。\n{e.Message}", "OK", default);
}
}

Expand Down

0 comments on commit 6dd9ce6

Please sign in to comment.