protected virtual async Task EmbedImagesAsync(IHtmlDocument doc, OpfFile opfFile, Chapter chapter, string outputDir) { var tasks = new List<Task>(); var images = new Dictionary<Uri, string>(); foreach (var img in doc.QuerySelectorAll("img")) { string src = img.GetAttribute("src"); if (src.StartsWith("//")) { src = src.Substring(2); if (!(src.StartsWith("http://") || src.StartsWith("https://"))) src = "http://" + src; } Uri uri; if (!Uri.TryCreate(src, UriKind.RelativeOrAbsolute, out uri)) continue; UriBuilder ub = new UriBuilder(uri) { Query = string.Empty }; uri = ub.Uri; string fileName = $"{Path.GetRandomFileName()}.{Path.GetExtension(uri.ToString())}".ToValidFilePath(); if (string.IsNullOrEmpty(fileName)) return; string path = Path.Combine(outputDir, fileName); if (!images.ContainsKey(uri)) images.Add(uri, path); string filePath = Path.Combine(new DirectoryInfo(outputDir).Name, Path.GetFileName(path)).Replace(@"\", "/"); img.SetAttribute("src", filePath); } foreach (var img in images) { tasks.Add(Task.Run(async () => { Uri uri = img.Key; string path = img.Value; string outputPath = Path.Combine(new DirectoryInfo(outputDir).Name, Path.GetFileName(path)).Replace(@"\", "/"); string src = uri.ToString(); if (uri.IsAbsoluteUri && !uri.IsFile) { try { using (HttpClient client = new HttpClient()) { HttpResponseMessage resp = await client.GetAsync(src); resp.EnsureSuccessStatusCode(); string mediaType = resp.Content.Headers.ContentType.MediaType.ToLower(); if (mediaType != MediaType.JpegType && mediaType != MediaType.PngType) return; if (File.Exists(path)) return; using (FileStream fs = new FileStream(path, FileMode.CreateNew)) await resp.Content.CopyToAsync(fs); } } catch (Exception) { return; } } else if (File.Exists(src)) { File.Copy(src, path); } MediaType mType = MediaType.FromExtension(Path.GetExtension(path)); if (mType == null) return; opfFile.AddItem(new OpfItem(outputPath, StringUtilities.GenerateRandomString(), mType), false); })); } await Task.WhenAll(tasks.ToArray()); chapter.Content = doc.QuerySelector("body").ChildNodes.ToHtml(new XmlMarkupFormatter()); }
public async Task GenerateEpubAsync(string epubDest, GenerateOptions options) { OpfFile opf = new OpfFile(new OpfMetadata { Title = { Text = Title }, Language = { Text = Language }, Description = { Text = Description }, Creator = { Text = Creator }, Publisher = { Text = Publisher } }); string tmpDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); string epub = Path.Combine(tmpDir, "EPUB"); string metaInf = Path.Combine(tmpDir, "META-INF"); Directory.CreateDirectory(tmpDir); Directory.CreateDirectory(epub); Directory.CreateDirectory(Path.Combine(epub, "covers")); Directory.CreateDirectory(Path.Combine(epub, "css")); Directory.CreateDirectory(Path.Combine(epub, "fonts")); Directory.CreateDirectory(Path.Combine(epub, "images")); Directory.CreateDirectory(metaInf); File.WriteAllText(Path.Combine(tmpDir, "mimetype"), "application/epub+zip"); Container container = new Container(); container.AddRootFile(new RootFile { FullPath = "EPUB/package.opf", MediaType = "application/oebps-package+xml" }); container.Save(Path.Combine(metaInf, "container.xml")); if (!string.IsNullOrEmpty(CoverImage)) { string coverExt = Path.GetExtension(CoverImage); MediaType mType = MediaType.FromExtension(coverExt); if (mType != MediaType.PngType && mType != MediaType.JpegType) throw new Exception("Invalid cover image extension!"); string coverImgFile = Path.GetFileName(CoverImage); string coverImg = Path.Combine("covers", coverImgFile); Uri coverImgUri; if (Uri.TryCreate(CoverImage, UriKind.RelativeOrAbsolute, out coverImgUri)) { if (!coverImgUri.IsFile) using (WebClient wc = new WebClient()) await wc.DownloadFileTaskAsync(CoverImage, Path.Combine(epub, coverImg)); else if (File.Exists(CoverImage)) File.Copy(CoverImage, Path.Combine(epub, coverImg)); OpfItem coverImageItem = new OpfItem(coverImg.Replace(@"\", "/"), Path.GetFileNameWithoutExtension(coverImg), mType) { Linear = false, Properties = "cover-image" }; OpfItem coverItem = new OpfItem("cover.xhtml", "cover", MediaType.XHtmlType); File.WriteAllText(Path.Combine(epub, "cover.xhtml"), RazorCompiler.Get(Templates[EBookTemplate.Cover], "cover", $"covers/{coverImgFile}")); opf.AddItem(coverItem); opf.AddItem(coverImageItem, false); } } TableOfContents toc = new TableOfContents { Title = Title }; OpfItem navItem = new OpfItem("toc.xhtml", "toc", MediaType.XHtmlType) { Properties = "nav" }; opf.AddItem(navItem); var parser = new HtmlParser(); foreach (Chapter chapter in Chapters) { var doc = parser.Parse(chapter.Content); if (options.EmbedImages) await EmbedImagesAsync(doc, opf, chapter, Path.Combine(epub, "images")); else chapter.Content = doc.QuerySelector("body").ChildNodes.ToHtml(new XmlMarkupFormatter()); string randomFile = Path.GetRandomFileName() + ".xhtml"; OpfItem item = new OpfItem(randomFile, StringUtilities.GenerateRandomString(6), MediaType.XHtmlType); opf.AddItem(item); File.WriteAllText(Path.Combine(epub, randomFile), RazorCompiler.Get(Templates[EBookTemplate.Chapter], "chapter", chapter)); toc.Sections.Add(new Section { Name = chapter.Name, Href = randomFile }); } string tocFile = Path.Combine(epub, "toc.xhtml"); File.WriteAllText(tocFile, RazorCompiler.Get(Templates[EBookTemplate.TableOfContents], "toc", toc)); opf.Save(Path.Combine(epub, "package.opf")); if (File.Exists(epubDest)) File.Delete(epubDest); using (FileStream fs = new FileStream(epubDest, FileMode.CreateNew)) using (ZipArchive za = new ZipArchive(fs, ZipArchiveMode.Create)) { za.CreateEntryFromFile(Path.Combine(tmpDir, "mimetype"), "mimetype", CompressionLevel.NoCompression); Zip(za, epub, "EPUB"); Zip(za, metaInf, "META-INF"); } Directory.Delete(tmpDir, true); }