/
plug1at
/
Personal_Digital_Library
Обзор
Документация
Войти
/
plug1at
/
Personal_Digital_Library
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
DigitalLibrary.Core/Services/MetadataExtractor.cs
668 строк
29 KB
plug1at
Update: MetadataExtractor.cs
11 июн 2026, 00:25
Верифицирован
11 июн 2026, 00:25
5a4528d
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Xml.Linq; using UglyToad.PdfPig; using System.Text.RegularExpressions; using System.Web; namespace DigitalLibrary.Core.Services { public class MetadataExtractor : IMetadataExtractor { public (string title, string author) ExtractMetadata(string filePath, string format) { return format.ToUpper() switch { "FB2" => ExtractFb2(filePath), "EPUB" => ExtractEpub(filePath), "PDF" => ExtractPdf(filePath), _ => ExtractFromFileName(filePath) }; } public Dictionary<string, string> ExtractAllMetadata(string filePath, string format) { return format.ToUpper() switch { "FB2" => ExtractFb2All(filePath), "EPUB" => ExtractEpubAll(filePath), "PDF" => ExtractPdfAll(filePath), "TXT" => new Dictionary<string, string> { ["Title"] = Path.GetFileNameWithoutExtension(filePath), ["Author"] = "����������� �����", ["Format"] = "TXT" }, _ => new Dictionary<string, string>() }; } private (string title, string author) ExtractFb2(string filePath) { try { var doc = XDocument.Load(filePath); var titleInfo = doc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "title-info"); if (titleInfo != null) { var title = titleInfo.Descendants() .FirstOrDefault(e => e.Name.LocalName == "book-title")?.Value ?? ""; var authorElement = titleInfo.Descendants() .FirstOrDefault(e => e.Name.LocalName == "author"); var firstName = authorElement?.Descendants() .FirstOrDefault(e => e.Name.LocalName == "first-name")?.Value ?? ""; var lastName = authorElement?.Descendants() .FirstOrDefault(e => e.Name.LocalName == "last-name")?.Value ?? ""; var author = $"{firstName} {lastName}".Trim(); return (title, string.IsNullOrEmpty(author) ? "����������� �����" : author); } } catch { } return ExtractFromFileName(filePath); } private Dictionary<string, string> ExtractFb2All(string filePath) { var metadata = new Dictionary<string, string>(); try { var doc = XDocument.Load(filePath); var titleInfo = doc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "title-info"); if (titleInfo != null) { metadata["Title"] = titleInfo.Descendants() .FirstOrDefault(e => e.Name.LocalName == "book-title")?.Value ?? ""; var authorElement = titleInfo.Descendants() .FirstOrDefault(e => e.Name.LocalName == "author"); if (authorElement != null) { var firstName = authorElement.Descendants() .FirstOrDefault(e => e.Name.LocalName == "first-name")?.Value ?? ""; var lastName = authorElement.Descendants() .FirstOrDefault(e => e.Name.LocalName == "last-name")?.Value ?? ""; metadata["Author"] = $"{firstName} {lastName}".Trim(); } metadata["Genre"] = string.Join(", ", titleInfo.Descendants() .Where(e => e.Name.LocalName == "genre") .Select(e => e.Value)); metadata["Year"] = titleInfo.Descendants() .FirstOrDefault(e => e.Name.LocalName == "year")?.Value ?? ""; metadata["Publisher"] = titleInfo.Descendants() .FirstOrDefault(e => e.Name.LocalName == "publisher")?.Value ?? ""; metadata["ISBN"] = titleInfo.Descendants() .FirstOrDefault(e => e.Name.LocalName == "isbn")?.Value ?? ""; metadata["Language"] = titleInfo.Descendants() .FirstOrDefault(e => e.Name.LocalName == "lang")?.Value ?? ""; metadata["Annotation"] = titleInfo.Descendants() .FirstOrDefault(e => e.Name.LocalName == "annotation")?.Value ?? ""; } } catch { } if (!metadata.ContainsKey("Title") || !metadata.ContainsKey("Author")) { var (fileNameTitle, fileNameAuthor) = ExtractFromFileName(filePath); if (!metadata.ContainsKey("Title")) metadata["Title"] = fileNameTitle; if (!metadata.ContainsKey("Author")) metadata["Author"] = fileNameAuthor; } return metadata; } private (string title, string author) ExtractEpub(string filePath) { try { using var archive = ZipFile.OpenRead(filePath); var containerEntry = archive.GetEntry("META-INF/container.xml"); if (containerEntry != null) { using var stream = containerEntry.Open(); var containerDoc = XDocument.Load(stream); var rootfilePath = containerDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "rootfile")? .Attribute("full-path")?.Value; if (rootfilePath != null) { var contentEntry = archive.GetEntry(rootfilePath); if (contentEntry != null) { using var contentStream = contentEntry.Open(); var contentDoc = XDocument.Load(contentStream); var title = contentDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "title")?.Value ?? ""; var creator = contentDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "creator")?.Value ?? ""; return (title, string.IsNullOrEmpty(creator) ? "����������� �����" : creator); } } } } catch { } return ExtractFromFileName(filePath); } private Dictionary<string, string> ExtractEpubAll(string filePath) { var metadata = new Dictionary<string, string>(); try { using var archive = ZipFile.OpenRead(filePath); var containerEntry = archive.GetEntry("META-INF/container.xml"); if (containerEntry != null) { using var stream = containerEntry.Open(); var containerDoc = XDocument.Load(stream); var rootfilePath = containerDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "rootfile")? .Attribute("full-path")?.Value; if (rootfilePath != null) { var contentEntry = archive.GetEntry(rootfilePath); if (contentEntry != null) { using var contentStream = contentEntry.Open(); var contentDoc = XDocument.Load(contentStream); metadata["Title"] = contentDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "title")?.Value ?? ""; metadata["Author"] = contentDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "creator")?.Value ?? ""; metadata["Publisher"] = contentDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "publisher")?.Value ?? ""; metadata["Language"] = contentDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "language")?.Value ?? ""; metadata["Description"] = contentDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "description")?.Value ?? ""; metadata["Date"] = contentDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "date")?.Value ?? ""; } } } } catch { } if (!metadata.ContainsKey("Title") || !metadata.ContainsKey("Author")) { var (fileNameTitle, fileNameAuthor) = ExtractFromFileName(filePath); if (!metadata.ContainsKey("Title")) metadata["Title"] = fileNameTitle; if (!metadata.ContainsKey("Author")) metadata["Author"] = fileNameAuthor; } return metadata; } private (string title, string author) ExtractPdf(string filePath) { try { using var pdfDocument = PdfDocument.Open(filePath); // �������� �������� ��������� �� ���������� PDF if (pdfDocument.Information.Title != null) { var title = pdfDocument.Information.Title; var author = pdfDocument.Information.Author ?? "����������� �����"; return (title, author); } // ���� ���������� ���, ����� ������ �������� ������ var firstPage = pdfDocument.GetPage(1); var text = firstPage.Text; if (!string.IsNullOrWhiteSpace(text)) { // ����� ������ �������� ������ ��� ��������� var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries); if (lines.Length > 0) { var title = lines[0].Trim(); if (title.Length > 100) title = title.Substring(0, 100); return (title, "����������� �����"); } } } catch { } return ExtractFromFileName(filePath); } private Dictionary<string, string> ExtractPdfAll(string filePath) { var metadata = new Dictionary<string, string>(); try { using var pdfDocument = PdfDocument.Open(filePath); metadata["Title"] = pdfDocument.Information.Title ?? ""; metadata["Author"] = pdfDocument.Information.Author ?? ""; metadata["Subject"] = pdfDocument.Information.Subject ?? ""; metadata["Keywords"] = pdfDocument.Information.Keywords ?? ""; metadata["Creator"] = pdfDocument.Information.Creator ?? ""; metadata["Producer"] = pdfDocument.Information.Producer ?? ""; var firstPage = pdfDocument.GetPage(1); metadata["PageCount"] = pdfDocument.NumberOfPages.ToString(); // ����� ������ 200 �������� ������ ��� ������ var text = firstPage.Text; if (!string.IsNullOrWhiteSpace(text)) { metadata["Preview"] = text.Length > 200 ? text.Substring(0, 200) + "..." : text; } } catch { } if (!metadata.ContainsKey("Title") || !metadata.ContainsKey("Author")) { var (fileNameTitle, fileNameAuthor) = ExtractFromFileName(filePath); if (!metadata.ContainsKey("Title")) metadata["Title"] = fileNameTitle; if (!metadata.ContainsKey("Author")) metadata["Author"] = fileNameAuthor; } return metadata; } private (string title, string author) ExtractFromFileName(string filePath) { var fileName = Path.GetFileNameWithoutExtension(filePath); // ������� ������ �����������: " - ", " � ", " � ", "-" var separators = new[] { " - ", " � ", " � ", "-" }; foreach (var separator in separators) { if (fileName.Contains(separator)) { var parts = fileName.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 2) { var part1 = parts[0].Trim(); var part2 = parts[1].Trim(); // �������� ����������, ��� �����, � ��� �������� // ������ ������: "����� - ��������" // ���� ������ ����� �������� ��������� ����� � �������� ����, �������� ��� ����� (������� ���) if (IsProbablyAuthor(part1) && !IsProbablyAuthor(part2)) { return (part2, part1); } // ���� ������ ����� � ������� � ��� ��� ��� ���.����, ��������� ������ ��� �������� if (part2.StartsWith("(") && part2.EndsWith(")")) { return (part1, "����������� �����"); } // �� ���������: ������ ����� � �����, ������ � �������� return (part2, part1); } } } // ���� ����������� �� ������, ���������� �� ��� �������� return (fileName, "����������� �����"); } private bool IsProbablyAuthor(string text) { // ���������, ������ �� ������ �� ��� ������ // ��������: ����� � ��������� �����, �� ������� ������� if (string.IsNullOrEmpty(text)) return false; var words = text.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (words.Length < 2 || words.Length > 4) return false; // ���� ������ ����� ���������� � ��������� ����� � ��������, ��� ��� foreach (var word in words) { if (word.Length == 0) return false; if (!char.IsUpper(word[0])) return false; } return true; } public string ExtractFullText(string filePath, string format) { try { return format.ToUpper() switch { "FB2" => ExtractFb2FullText(filePath), "EPUB" => ExtractEpubFullText(filePath), "PDF" => ExtractPdfFullText(filePath), "TXT" => ExtractTxtFullText(filePath), _ => string.Empty }; } catch { return string.Empty; } } private string ExtractFb2FullText(string filePath) { try { var doc = XDocument.Load(filePath); var body = doc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "body"); if (body != null) { return string.Join(" ", body.Descendants() .Where(e => e.Name.LocalName == "p") .Select(e => e.Value) .Take(500)); // ������������ ��� ������������������ } } catch { } return string.Empty; } private string ExtractEpubFullText(string filePath) { try { using var archive = ZipFile.OpenRead(filePath); var textParts = new List<string>(); foreach (var entry in archive.Entries) { if (entry.FullName.EndsWith(".xhtml") || entry.FullName.EndsWith(".html") || entry.FullName.EndsWith(".htm")) { using var stream = entry.Open(); var reader = new StreamReader(stream); var content = reader.ReadToEnd(); // ������� HTML-���� ������� �������� var text = System.Text.RegularExpressions.Regex.Replace(content, "<[^>]+>", " "); text = System.Web.HttpUtility.HtmlDecode(text); text = System.Text.RegularExpressions.Regex.Replace(text, @"\s+", " "); textParts.Add(text.Trim()); } if (textParts.Sum(t => t.Length) > 50000) break; // ����������� } return string.Join(" ", textParts); } catch { } return string.Empty; } private string ExtractPdfFullText(string filePath) { try { using var pdfDocument = UglyToad.PdfPig.PdfDocument.Open(filePath); var textParts = new List<string>(); foreach (var page in pdfDocument.GetPages().Take(10)) // ���� 50 { textParts.Add(page.Text); if (textParts.Sum(t => t.Length) > 50000) break; // ���� 100000 } return string.Join(" ", textParts); } catch { } return string.Empty; } private string ExtractTxtFullText(string filePath) { try { var text = File.ReadAllText(filePath); if (text.Length > 50000) // ���� 100000 text = text.Substring(0, 50000); return text; } catch { } return string.Empty; } public byte[]? ExtractCover(string filePath, string format) { try { return format.ToUpper() switch { "FB2" => ExtractFb2Cover(filePath), "EPUB" => ExtractEpubCover(filePath), "PDF" => ExtractPdfCover(filePath), _ => null }; } catch { return null; } } private byte[]? ExtractFb2Cover(string filePath) { try { var doc = XDocument.Load(filePath); // ���� coverpage var coverPage = doc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "coverpage"); if (coverPage != null) { var image = coverPage.Descendants() .FirstOrDefault(e => e.Name.LocalName == "image"); if (image != null) { var href = image.Attribute(XNamespace.Get("http://www.w3.org/1999/xlink") + "href")?.Value; if (href != null && href.StartsWith("#")) { var id = href.TrimStart('#'); var binary = doc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "binary" && e.Attribute("id")?.Value == id); if (binary != null && !string.IsNullOrEmpty(binary.Value)) { return Convert.FromBase64String(binary.Value); } } } } // ���� coverpage ���, ���� ������ ����������� � ���� ����� var firstImage = doc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "image"); if (firstImage != null) { var href = firstImage.Attribute(XNamespace.Get("http://www.w3.org/1999/xlink") + "href")?.Value; if (href != null && href.StartsWith("#")) { var id = href.TrimStart('#'); var binary = doc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "binary" && e.Attribute("id")?.Value == id); if (binary != null && !string.IsNullOrEmpty(binary.Value)) { return Convert.FromBase64String(binary.Value); } } } } catch { } return null; } private byte[]? ExtractEpubCover(string filePath) { try { using var archive = ZipFile.OpenRead(filePath); // ������� content.opf var containerEntry = archive.GetEntry("META-INF/container.xml"); if (containerEntry == null) return null; using var stream = containerEntry.Open(); var containerDoc = XDocument.Load(stream); var rootfilePath = containerDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "rootfile")? .Attribute("full-path")?.Value; if (rootfilePath == null) return null; var opfEntry = archive.GetEntry(rootfilePath); if (opfEntry == null) return null; using var opfStream = opfEntry.Open(); var opfDoc = XDocument.Load(opfStream); // ���������� ������� ���������� OPF-����� var basePath = Path.GetDirectoryName(rootfilePath); if (!string.IsNullOrEmpty(basePath)) basePath += "/"; else basePath = ""; // ���� ������� ����� meta name="cover" var coverMeta = opfDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "meta" && e.Attribute("name")?.Value == "cover"); string? coverImageId = null; if (coverMeta != null) { coverImageId = coverMeta.Attribute("content")?.Value; } // ���� �� ����� ����� meta, ���� ������� � id="cover-image" if (coverImageId == null) { var coverItem = opfDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "item" && (e.Attribute("id")?.Value == "cover-image" || e.Attribute("id")?.Value == "cover" || e.Attribute("properties")?.Value == "cover-image")); if (coverItem != null) { coverImageId = coverItem.Attribute("id")?.Value; } } // ���� �� ��� ���, ���� ������ ����������� � manifest if (coverImageId == null) { var firstImageItem = opfDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "item" && e.Attribute("media-type")?.Value.StartsWith("image/") == true); if (firstImageItem != null) { coverImageId = firstImageItem.Attribute("id")?.Value; } } // ������� href ����������� �� id if (coverImageId != null) { var imageItem = opfDoc.Descendants() .FirstOrDefault(e => e.Name.LocalName == "item" && e.Attribute("id")?.Value == coverImageId); if (imageItem != null) { var href = imageItem.Attribute("href")?.Value; if (href != null) { var fullPath = basePath + href; var imageEntry = archive.GetEntry(fullPath); if (imageEntry != null) { using var imageStream = imageEntry.Open(); using var ms = new MemoryStream(); imageStream.CopyTo(ms); return ms.ToArray(); } } } } } catch { } return null; } private byte[]? ExtractPdfCover(string filePath) { try { using (var document = PdfDocument.Open(filePath)) { var firstPage = document.GetPages().FirstOrDefault(); if (firstPage == null) return null; foreach (var image in firstPage.GetImages()) { if (image.TryGetPng(out byte[] pngBytes)) { if (pngBytes.Length > 5000) { return pngBytes; } } var rawBytes = image.RawBytes; if (rawBytes != null && rawBytes.Length > 5000) { return rawBytes.ToArray(); } } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Ошибка извлечения обложки PDF: {ex.Message}"); } return null; } } }