diff --git a/Epub/KoeBook.Epub/ScrapingAozora.cs b/Epub/KoeBook.Epub/ScrapingAozora.cs index 765afda..ce6406f 100644 --- a/Epub/KoeBook.Epub/ScrapingAozora.cs +++ b/Epub/KoeBook.Epub/ScrapingAozora.cs @@ -286,7 +286,7 @@ public async Task ScrapingAsync(string url, string coverFilePath, var response = await downloading.Task.ConfigureAwait(false); using var ms = new MemoryStream(); await response.Content.CopyToAsync(ms, ct).ConfigureAwait(false); - var filePass = imageDirectory + FileUrlToFileName().Replace(img.Source, "$1"); + var filePass = System.IO.Path.Combine(imageDirectory, FileUrlToFileName().Replace(img.Source, "$1")); File.WriteAllBytes(filePass, ms.ToArray()); checkSection(document, chapterNum); if (document.Chapters[chapterNum].Sections[sectionNum].Elements.Count > 1) diff --git a/Epub/KoeBook.Epub/ScrapingNarou.cs b/Epub/KoeBook.Epub/ScrapingNarou.cs new file mode 100644 index 0000000..27ae164 --- /dev/null +++ b/Epub/KoeBook.Epub/ScrapingNarou.cs @@ -0,0 +1,298 @@ +using AngleSharp; +using AngleSharp.Dom; +using AngleSharp.Html.Dom; +using AngleSharp.Io; +using KoeBook.Epub.Service; +using System.IO; +using System.Net.Http.Json; +using static KoeBook.Epub.ScrapingHelper; + +namespace KoeBook.Epub +{ + public partial class ScrapingNarouService : IScrapingService + { + public ScrapingNarouService(IHttpClientFactory httpClientFactory) + { + _httpCliantFactory = httpClientFactory; + } + + private readonly IHttpClientFactory _httpCliantFactory; + + public async Task ScrapingAsync(string url, string coverFilePath, string imageDirectory, Guid id, CancellationToken ct) + { + var config = Configuration.Default.WithDefaultLoader(); + using var context = BrowsingContext.New(config); + var doc = await context.OpenAsync(url, ct).ConfigureAwait(false); + + // title の取得 + var bookTitle = doc.QuerySelector(".novel_title"); + if (bookTitle is null) + { + throw new EpubDocumentException($"Failed to get title properly.\nUrl may be not collect"); + } + + // auther の取得 + var bookAuther = doc.QuerySelector(".novel_writername a"); + if (bookAuther is null) + { + throw new EpubDocumentException($"Failed to get auther properly.\nUrl may be not collect"); + } + + bool isRensai = true; + int allNum = 0; + + var apiUrl = $"https://api.syosetu.com/novelapi/api/?of=ga-nt&ncode={UrlToNcode().Replace(url, "$1")}&out=json"; + + // APIを利用して、noveltype : 連載(1)か短編(2)か、general_all_no : 全掲載部分数 + var message = new HttpRequestMessage(System.Net.Http.HttpMethod.Get, apiUrl); + message.Headers.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"); + var client = _httpCliantFactory.CreateClient(); + var result = await client.SendAsync(message, ct).ConfigureAwait(false); + var test = await result.Content.ReadAsStringAsync(ct).ConfigureAwait(false); + if (result.IsSuccessStatusCode) + { + var content = await result.Content.ReadFromJsonAsync(ct).ConfigureAwait(false); + if (content != null) + { + if (content[1].noveltype != null) + { + if (content[1].noveltype == 2) + { + isRensai = false; + } + } + else + { + throw new EpubDocumentException("faild to get data by Narou API"); + } + if (content[1].general_all_no != null) + { + allNum = (int)content[1].general_all_no!; + } + if (allNum == 0) + { + throw new EpubDocumentException("faild to get data by Narou API"); + } + } + } + else + { + throw new EpubDocumentException("Url may be not Correct"); + } + + var document = new EpubDocument(bookTitle.InnerHtml, bookAuther.InnerHtml, coverFilePath, id); + if (isRensai) + { + List SectionWithChapterTitleList = new List(); + for (int i = 1; i <= allNum; i++) + { + Console.WriteLine(i); + await Task.Delay(500); + var pageUrl = System.IO.Path.Combine(url, i.ToString()); + var load = await ReadPageAsync(pageUrl, isRensai, imageDirectory, ct).ConfigureAwait(false); + SectionWithChapterTitleList.Add(load); + } + string? chapterTitle = null; + foreach (var sectionWithChapterTitle in SectionWithChapterTitleList) + { + if (sectionWithChapterTitle != null) + { + if (sectionWithChapterTitle.title != null) + { + if (sectionWithChapterTitle.title != chapterTitle) + { + chapterTitle = sectionWithChapterTitle.title; + document.Chapters.Add(new Chapter() { Title = chapterTitle }); + document.Chapters[^1].Sections.Add(sectionWithChapterTitle.section); + } + else + { + document.Chapters[^1].Sections.Add(sectionWithChapterTitle.section); + } + } + else + { + if (document.Chapters.Count == 0) + { + document.Chapters.Add(new Chapter()); + } + document.Chapters[^1].Sections.Add(sectionWithChapterTitle.section); + } + } + else + { + throw new EpubDocumentException("failed to get page"); + } + } + } + else + { + var load = await ReadPageAsync(url, isRensai, imageDirectory, ct).ConfigureAwait(false); + if (load != null) + { + document.Chapters.Add(new Chapter() { Title = null }); + document.Chapters[^1].Sections.Add(load.section); + } + } + return document; + } + + public record BookInfo(int? allcount, int? noveltype, int? general_all_no); + + private record SectionWithChapterTitle(string? title, Section section); + + private async Task ReadPageAsync(string url, bool isRensai, string imageDirectory, CancellationToken ct) + { + var config = Configuration.Default.WithDefaultLoader(); + using var context = BrowsingContext.New(config); + var doc = await context.OpenAsync(url, ct).ConfigureAwait(false); + + var chapterTitleElement = doc.QuerySelector(".chapter_title"); + string? chapterTitle = null; + if (chapterTitleElement != null) + { + if (chapterTitleElement.InnerHtml != null) + { + chapterTitle = chapterTitleElement.InnerHtml; + } + } + + IElement? sectionTitleElement = null; + if (isRensai) + { + sectionTitleElement = doc.QuerySelector(".novel_subtitle"); + } + else + { + sectionTitleElement = doc.QuerySelector(".novel_title"); + } + + string sectionTitle = ""; + if (sectionTitleElement == null) + { + throw new EpubDocumentException("Can not find title of page"); + } + else + { + sectionTitle = sectionTitleElement.InnerHtml; + } + + + var section = new Section(sectionTitleElement.InnerHtml); + + + var main_text = doc.QuerySelector("#novel_honbun"); + if (main_text != null) + { + foreach (var item in main_text.Children) + { + if (item is IHtmlParagraphElement) + { + if (item.ChildElementCount == 0) + { + if (!string.IsNullOrWhiteSpace(item.InnerHtml)) + { + foreach (var split in SplitBrace(item.InnerHtml)) + { + section.Elements.Add(new Paragraph() { Text = split }); + } + } + } + else if (item.ChildElementCount == 1) + { + if (item.Children[0] is IHtmlAnchorElement aElement) + { + if (aElement.ChildElementCount == 1) + { + if (aElement.Children[0] is IHtmlImageElement img) + { + if (img.Source != null) + { + // 画像のダウンロード + var loader = context.GetService(); + if (loader != null) + { + var downloading = loader.FetchAsync(new DocumentRequest(new Url(img.Source))); + ct.Register(() => downloading.Cancel()); + var response = await downloading.Task.ConfigureAwait(false); + using var ms = new MemoryStream(); + await response.Content.CopyToAsync(ms, ct).ConfigureAwait(false); + var filePass = System.IO.Path.Combine(imageDirectory, FileUrlToFileName().Replace(response.Address.Href, "$1")); + File.WriteAllBytes(filePass, ms.ToArray()); + section.Elements.Add(new Picture(filePass)); + } + } + else + { + throw new EpubDocumentException("Unexpected structure"); + } + } + } + else + { + throw new EpubDocumentException("Unexpected structure"); + } + } + else if (item.Children[0].TagName == "RUBY") + { + if (!string.IsNullOrWhiteSpace(item.InnerHtml)) + { + foreach (var split in SplitBrace(item.InnerHtml)) + { + section.Elements.Add(new Paragraph() { Text = split }); + } + } + } + else if (item.Children[0] is not IHtmlBreakRowElement) + { + throw new EpubDocumentException("Unexpected structure"); + } + } + else + { + bool isAllRuby = true; + foreach (var tags in item.Children) + { + if (tags.TagName != "RUBY") + { + isAllRuby = false; + } + } + if (isAllRuby) + { + if (!string.IsNullOrWhiteSpace(item.InnerHtml)) + { + foreach (var split in SplitBrace(item.InnerHtml)) + { + section.Elements.Add(new Paragraph() { Text = split }); + } + } + } + else + { + throw new EpubDocumentException("Unexpected structure"); + } + } + } + else + { + throw new EpubDocumentException("Unexpected structure"); + } + } + } + else + { + throw new EpubDocumentException("There is no honbun."); + } + return new SectionWithChapterTitle(chapterTitle, section); + } + + + + [System.Text.RegularExpressions.GeneratedRegex(@"https://.{5,7}.syosetu.com/(.{7}).?")] + private static partial System.Text.RegularExpressions.Regex UrlToNcode(); + + [System.Text.RegularExpressions.GeneratedRegex(@"http.{1,}/([^/]{0,}\.[^/]{1,})")] + private static partial System.Text.RegularExpressions.Regex FileUrlToFileName(); + } +} diff --git a/KoeBook.Core/Services/AnalyzerService _CoverFileBytes.cs b/KoeBook.Core/Services/AnalyzerService _CoverFileBytes.cs new file mode 100644 index 0000000..4edc65d --- /dev/null +++ b/KoeBook.Core/Services/AnalyzerService _CoverFileBytes.cs @@ -0,0 +1,13 @@ +using System.Text; +using System.Text.RegularExpressions; +using KoeBook.Core.Contracts.Services; +using KoeBook.Core.Models; +using KoeBook.Epub; +using KoeBook.Epub.Service; + +namespace KoeBook.Core.Services; + +public partial class AnalyzerService +{ + public ReadOnlySpan CoverFile => [89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x1, 0x2c, 0x0, 0x0, 0x1, 0x90, 0x8, 0x6, 0x0, 0x0, 0x0, 0x14, 0x75, 0x5d, 0x7b, 0x0, 0x0, 0x0, 0x1, 0x73, 0x52, 0x47, 0x42, 0x0, 0xae, 0xce, 0x1c, 0xe9, 0x0, 0x0, 0x0, 0x4, 0x67, 0x41, 0x4d, 0x41, 0x0, 0x0, 0xb1, 0x8f, 0xb, 0xfc, 0x61, 0x5, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0x1d, 0x87, 0x0, 0x0, 0x1d, 0x87, 0x1, 0x8f, 0xe5, 0xf1, 0x65, 0x0, 0x0, 0x4, 0xfa, 0x49, 0x44, 0x41, 0x54, 0x78, 0x5e, 0xed, 0xd4, 0x1, 0xd, 0x0, 0x0, 0xc, 0xc3, 0xa0, 0xfb, 0x37, 0xbd, 0xeb, 0x68, 0x2, 0x22, 0xb8, 0x1, 0x44, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x32, 0x84, 0x5, 0x64, 0x8, 0xb, 0xc8, 0x10, 0x16, 0x90, 0x21, 0x2c, 0x20, 0x43, 0x58, 0x40, 0x86, 0xb0, 0x80, 0xc, 0x61, 0x1, 0x19, 0xc2, 0x2, 0x22, 0xb6, 0x7, 0x7c, 0x3f, 0x1a, 0x75, 0x8a, 0x71, 0x90, 0x2f, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]; +} diff --git a/KoeBook.Core/Services/AnalyzerService.cs b/KoeBook.Core/Services/AnalyzerService.cs index 7514cc1..3491aff 100644 --- a/KoeBook.Core/Services/AnalyzerService.cs +++ b/KoeBook.Core/Services/AnalyzerService.cs @@ -16,6 +16,11 @@ public partial class AnalyzerService(IScrapingService scrapingService, IEpubDocu public async ValueTask AnalyzeAsync(BookProperties bookProperties, string tempDirectory, string coverFilePath, CancellationToken cancellationToken) { + coverFilePath = Path.Combine(tempDirectory, "Cover.png"); + using var fs = File.Create(coverFilePath); + await fs.WriteAsync(CoverFile.ToArray(), cancellationToken); + await fs.FlushAsync(cancellationToken); + EpubDocument? document; try { diff --git a/KoeBook/Services/GenerationTaskRunnerService.cs b/KoeBook/Services/GenerationTaskRunnerService.cs index 5029174..fe6f0fd 100644 --- a/KoeBook/Services/GenerationTaskRunnerService.cs +++ b/KoeBook/Services/GenerationTaskRunnerService.cs @@ -50,7 +50,7 @@ private async ValueTask RunAsync(GenerationTask task) { try { - var scripts = await _analyzerService.AnalyzeAsync(new(task.Id, task.Source, task.SourceType), "", "", task.CancellationToken); + 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; @@ -59,8 +59,10 @@ private async ValueTask RunAsync(GenerationTask task) { var resultPath = await _epubGenService.GenerateEpubAsync(scripts, _tempFolder, task.CancellationToken); task.State = GenerationState.Completed; - task.Progress = 0; - task.MaximumProgress = 0; + 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) @@ -88,6 +90,8 @@ public async void RunGenerateEpubAsync(GenerationTask task) 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) { diff --git a/KoeBook/ViewModels/MainViewModel.cs b/KoeBook/ViewModels/MainViewModel.cs index bda5ed5..912ad60 100644 --- a/KoeBook/ViewModels/MainViewModel.cs +++ b/KoeBook/ViewModels/MainViewModel.cs @@ -153,7 +153,8 @@ static bool IsValid(string? value) if (string.IsNullOrEmpty(value)) return true; ReadOnlySpan allowedOrigins = [ - "https://www.aozora.gr.jp" + "https://www.aozora.gr.jp", + "https://syosetu.com", ]; try diff --git a/KoeBook/Views/MainPage.xaml b/KoeBook/Views/MainPage.xaml index 15af6f8..fcaecb5 100644 --- a/KoeBook/Views/MainPage.xaml +++ b/KoeBook/Views/MainPage.xaml @@ -54,15 +54,15 @@ --> - + + PlaceholderText="https://www.aozora.gr.jp"/> diff --git a/README.md b/README.md index bdf59a2..0fbc39c 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,45 @@ -# プロダクト名 - +# KoeBook -![プロダクト名](https://kc3.me/cms/wp-content/uploads/2023/11/2b1b6d9083182c0ce0aeb60000b4d7a7.png) +![KoeBook](https://kc3.me/cms/wp-content/uploads/2023/11/2b1b6d9083182c0ce0aeb60000b4d7a7.png) ## チーム名 -チーム○ XXXX - +チームH OUCC KC3 Hack 出張部 -## 背景・課題・解決されること - - +## 目的 +- どんな本でも高品質の音声で聞けるようにする +- 家事や運転中でも、ラジオのように聞けるようにする +- 視覚障害者がどんな本でもアクセスできるようにする ## プロダクト説明 - - +[青空文庫](https://www.aozora.gr.jp/)や[小説家になろう](https://syosetu.com/)にある小説の読み上げ音声をAIによって生成し、EPUBとして出力します。 +AIを用いて話者を特定して適切な音声を生成するので場面にあった音声を生成できます。 -## 操作説明・デモ動画 -[デモ動画はこちら](https://www.youtube.com/watch?v=_FAA15ARmas) - +## 操作説明 +1. 最初の画面で音声朗読させたい青空文庫か小説家になろうの作品のリンクを張る + - 青空文庫は図書カードページのxhtmlファイルのリンク + - 小説家になろうは目次のページ +2. GPTの解析が終わるまで放置 +3. (やりたい人は)セリフに対応するキャラクターの編集 +4. 音声合成&電子書籍ファイルが完成するまで放置 +5. できた電子書籍ファイルをお好みのリーダで読む ## 注力したポイント - - +- WinUI3を用いてモダンでわかりやすいデザインのUIを作成しました。 +- GPT4を用いて登場人物の推定+セリフの関連づけを行えるようにしたこと。 +- Style-Bert-VITS2を用いてより人間らしい音声で朗読できること。 +- 青空文庫やなろうのスクレイビングを行って最適なルビの処理を行います。 ## 使用技術 - - - - +- WinUI 3 +- GPT4 +- Style-Bert-VITS2 +- AngleSharp +- Epub