Skip to content

Commit

Permalink
Merge branch 'main' into feat/#33
Browse files Browse the repository at this point in the history
  • Loading branch information
TakenPt committed May 1, 2024
2 parents 3aa0032 + c288d43 commit 11773fe
Show file tree
Hide file tree
Showing 37 changed files with 901 additions and 100 deletions.
40 changes: 40 additions & 0 deletions Epub/KoeBook.Epub/Services/AiStoryAnalyzerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using KoeBook.Epub.Contracts.Services;
using KoeBook.Epub.Models;
using KoeBook.Models;

namespace KoeBook.Epub.Services;

public partial class AiStoryAnalyzerService(ISplitBraceService splitBraceService)
{
private readonly ISplitBraceService _splitBraceService = splitBraceService;


public EpubDocument CreateEpubDocument(AiStory aiStory, Guid id)
{
return new EpubDocument(aiStory.Title, "AI", "", id)
{
Chapters = [new Chapter()
{
Sections = [
new Section("本編")
{
Elements = aiStory.Lines.SelectMany(s =>
s.SelectMany(p => _splitBraceService.SplitBrace(p.GetText())
.Zip(_splitBraceService.SplitBrace(p.GetScript()))
.Select(Element (p) => new Paragraph
{
Text = p.First,
ScriptLine = new(p.Second, "", "")
}))
.Append(new Paragraph()
{
Text = "",
ScriptLine = new("", "", "")
})
).ToList(),
}
]
}]
};
}
}
60 changes: 31 additions & 29 deletions Epub/KoeBook.Epub/Services/AnalyzerService.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
using KoeBook.Core;
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Models;
using KoeBook.Epub.Contracts.Services;
using KoeBook.Epub.Models;
using KoeBook.Models;

namespace KoeBook.Epub.Services;

public partial class AnalyzerService(IScraperSelectorService scrapingService, IEpubDocumentStoreService epubDocumentStoreService, ILlmAnalyzerService llmAnalyzerService) : IAnalyzerService
public partial class AnalyzerService(
IScraperSelectorService scrapingService,
IEpubDocumentStoreService epubDocumentStoreService,
ILlmAnalyzerService llmAnalyzerService,
AiStoryAnalyzerService aiStoryAnalyzerService) : IAnalyzerService
{
private readonly IScraperSelectorService _scrapingService = scrapingService;
private readonly IEpubDocumentStoreService _epubDocumentStoreService = epubDocumentStoreService;
private readonly ILlmAnalyzerService _llmAnalyzerService = llmAnalyzerService;
private readonly AiStoryAnalyzerService _aiStoryAnalyzerService = aiStoryAnalyzerService;

public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties, string tempDirectory, CancellationToken cancellationToken)
{
Expand All @@ -22,50 +28,46 @@ public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties,
await fs.WriteAsync(CoverFile.ToArray(), cancellationToken);
await fs.FlushAsync(cancellationToken);

EpubDocument? document;
var rubyReplaced = false;
EpubDocument document;
try
{
document = await _scrapingService.ScrapingAsync(bookProperties.Source, coverFilePath, tempDirectory, bookProperties.Id, cancellationToken);
}
catch (EbookException)
{
throw;
switch (bookProperties)
{
case { SourceType: SourceType.Url or SourceType.FilePath, Source: string uri }:
document = await _scrapingService.ScrapingAsync(uri, coverFilePath, tempDirectory, bookProperties.Id, cancellationToken);
break;
case { SourceType: SourceType.AiStory, Source: AiStory aiStory }:
document = _aiStoryAnalyzerService.CreateEpubDocument(aiStory, bookProperties.Id);
rubyReplaced = true;
break;
default:
throw new UnreachableException($"SourceType: {bookProperties.SourceType}, Source: {bookProperties.Source}");
}
}
catch (EbookException) { throw; }
catch (Exception ex)
{
EbookException.Throw(ExceptionType.WebScrapingFailed, innerException: ex);
return default;
throw new EbookException(ExceptionType.WebScrapingFailed, innerException: ex);
}
_epubDocumentStoreService.Register(document, cancellationToken);

var scriptLines = document.Chapters.SelectMany(c => c.Sections)
.SelectMany(s => s.Elements)
.OfType<Paragraph>()
.Select(p =>
.Select<Paragraph, ScriptLine>(rubyReplaced
? p => p.ScriptLine!
: p =>
{
// ルビを置換
var line = ReplaceBaseTextWithRuby(p.Text);

return p.ScriptLine = new ScriptLine(line, "", "");
}).ToList();

// 800文字以上になったら1チャンクに分ける
var chunks = new List<string>();
var chunk = new StringBuilder();
foreach (var line in scriptLines)
{
if (chunk.Length + line.Text.Length > 800)
{
chunks.Add(chunk.ToString());
chunk.Clear();
}
chunk.AppendLine(line.Text);
}
if (chunk.Length > 0) chunks.Add(chunk.ToString());

// GPT4による話者、スタイル解析
var bookScripts = await _llmAnalyzerService.LlmAnalyzeScriptLinesAsync(bookProperties, scriptLines, chunks, cancellationToken);
}).Where(l => !string.IsNullOrEmpty(l.Text))
.ToArray();

// LLMによる話者、スタイル解析
var bookScripts = await _llmAnalyzerService.LlmAnalyzeScriptLinesAsync(bookProperties, scriptLines, cancellationToken)!;
return bookScripts;
}

Expand Down
7 changes: 2 additions & 5 deletions Epub/KoeBook.Epub/Services/ScrapingAozoraService.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
using System.Reflection.Metadata;
using System.Text;
using System.Xml.Linq;
using System.Text;
using AngleSharp;
using AngleSharp.Dom;
using AngleSharp.Html.Dom;
using AngleSharp.Io;
using KoeBook.Core;
using KoeBook.Core.Utilities;
using KoeBook.Epub.Contracts.Services;
Expand Down Expand Up @@ -63,7 +60,7 @@ public async ValueTask<EpubDocument> ScrapingAsync(string url, string coverFileP
var nextNode = element.NextSibling;
switch (element.TagName)
{
case TagNames.A:
case TagNames.Br:
if (previous)
{
document.EnsureSection(chapterNum);
Expand Down
8 changes: 8 additions & 0 deletions KoeBook.Core/Contracts/Services/IClaudeService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Claudia;

namespace KoeBook.Core.Contracts.Services;

public interface IClaudeService
{
IMessages? Messages { get; }
}
19 changes: 19 additions & 0 deletions KoeBook.Core/Contracts/Services/ICreateCoverFileService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace KoeBook.Core.Contracts.Services;

public interface ICreateCoverFileService
{
/// <summary>
/// 表紙用の画像を作成
/// </summary>
/// <param name="title">作品の題名</param>
/// <param name="author">作品の著者名</param>
/// <param name="coverFilePath">表紙の画像を置くフォルダのパス</param>
/// <returns>成功すれば、true、失敗すれば、false</returns>
void Create(string title, string author, string coverFilePath);
}
2 changes: 1 addition & 1 deletion KoeBook.Core/Contracts/Services/ILlmAnalyzerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace KoeBook.Core.Contracts.Services;

public interface ILlmAnalyzerService
{
ValueTask<BookScripts> LlmAnalyzeScriptLinesAsync(BookProperties bookProperties, List<ScriptLine> scriptLines, List<string> chunks, CancellationToken cancellationToken);
ValueTask<BookScripts> LlmAnalyzeScriptLinesAsync(BookProperties bookProperties, ScriptLine[] scriptLines, CancellationToken cancellationToken);
}
9 changes: 9 additions & 0 deletions KoeBook.Core/Contracts/Services/IStoryCreatorService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using KoeBook.Core.Models;

namespace KoeBook.Core.Contracts.Services;

public interface IStoryCreatorService
{
/// <returns>XML</returns>
public ValueTask<string> CreateStoryAsync(StoryGenre genre, string instruction, CancellationToken cancellationToken);
}
12 changes: 12 additions & 0 deletions KoeBook.Core/EbookException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ public enum ExceptionType
[EnumMember(Value = "GPT4による話者・スタイル設定に失敗しました")]
Gpt4TalkerAndStyleSettingFailed,

[EnumMember(Value = "APIキーが設定されていません")]
ApiKeyNotSet,

[EnumMember(Value = "Claudeによる話者・スタイル設定に失敗しました")]
ClaudeTalkerAndStyleSettingFailed,

[EnumMember(Value = "webページの解析に失敗しました")]
WebScrapingFailed,

Expand All @@ -62,4 +68,10 @@ public enum ExceptionType
/// </summary>
[EnumMember(Value = "無効なURLです")]
InvalidUrl,

[EnumMember(Value = "不正なXMLです")]
InvalidXml,

[EnumMember(Value = "表紙の画像の生成に失敗しました")]
CreateCoverFileFailed,
}
3 changes: 2 additions & 1 deletion KoeBook.Core/KoeBook.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Betalgo.OpenAI" Version="7.4.6" />
<PackageReference Include="Betalgo.OpenAI" Version="8.1.1" />
<PackageReference Include="Claudia" Version="1.2.0" />
<PackageReference Include="FastEnum" Version="1.8.0" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
Expand Down
50 changes: 50 additions & 0 deletions KoeBook.Core/Models/AiStory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Xml.Serialization;

namespace KoeBook.Models;

[XmlRoot("Book")]
public class AiStory
{

[XmlElement("Title", typeof(string), IsNullable = false)]
public string Title { get; init; } = "";

[XmlArray("Content", IsNullable = false)]
[XmlArrayItem("Section", IsNullable = false)]
[XmlArrayItem("Paragraph", IsNullable = false, NestingLevel = 1)]
public Line[][] Lines { get; init; } = [];

public record Line(
[property: XmlElement("Text", typeof(Text), IsNullable = false), XmlElement("Ruby", typeof(Ruby), IsNullable = false)] InlineElement[] Inlines)
{
private Line() : this([]) { }

public string GetText() => string.Concat(Inlines.Select(e => e.Html));

public string GetScript() => string.Concat(Inlines.Select(e => e.Script));
}

public abstract record class InlineElement
{
public abstract string Html { get; }
public abstract string Script { get; }
}

public record Text([property: XmlText] string InnerText) : InlineElement
{
private Text() : this("") { }

public override string Html => InnerText;
public override string Script => InnerText;
}

public record Ruby(
[property: XmlElement("Rb", IsNullable = false)] string Rb,
[property: XmlElement("Rt", IsNullable = false)] string Rt) : InlineElement
{
private Ruby() : this("", "") { }

public override string Html => $"<ruby>{Rb}<rt>{Rt}</rt></ruby>";
public override string Script => Rt;
}
}
31 changes: 26 additions & 5 deletions KoeBook.Core/Models/BookProperties.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
namespace KoeBook.Core.Models;
using KoeBook.Models;

namespace KoeBook.Core.Models;

/// <summary>
/// 読み上げる本の情報
/// </summary>
public class BookProperties(Guid id, string source, SourceType sourceType)
public class BookProperties
{
public Guid Id { get; } = id;
public BookProperties(Guid id, string source, SourceType sourceType)
{
if (sourceType != SourceType.FilePath && sourceType != SourceType.Url)
throw new ArgumentException($"{nameof(sourceType)}{nameof(SourceType.FilePath)}{nameof(SourceType.Url)}である必要があります。");
Id = id;
Source = source;
SourceType = sourceType;
}

public BookProperties(Guid id, AiStory aiStory)
{
Id = id;
Source = aiStory;
SourceType = SourceType.AiStory;
}

public Guid Id { get; }

public string Source { get; } = source;
/// <summary>
/// UriまたはAiStory
/// </summary>
public object Source { get; }

public SourceType SourceType { get; } = sourceType;
public SourceType SourceType { get; }
}
6 changes: 4 additions & 2 deletions KoeBook.Core/Models/BookScripts.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace KoeBook.Core.Models;
using System.Collections.Immutable;

namespace KoeBook.Core.Models;

/// <summary>
/// 本の読み上げ情報
Expand All @@ -15,5 +17,5 @@ public class BookScripts(BookProperties bookProperties, BookOptions options)
/// <summary>
/// 読み上げテキストの配列
/// </summary>
public required IReadOnlyList<ScriptLine> ScriptLines { get; set; }
public required ImmutableArray<ScriptLine> ScriptLines { get; set; }
}
3 changes: 3 additions & 0 deletions KoeBook.Core/Models/SourceType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ public enum SourceType

[EnumMember(Value = "ローカルファイル")]
FilePath,

[EnumMember(Value = "AI生成")]
AiStory,
}
4 changes: 4 additions & 0 deletions KoeBook.Core/Models/StoryGenre.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace KoeBook.Core.Models;

public record class StoryGenre(string Genre, string Description);

Loading

0 comments on commit 11773fe

Please sign in to comment.