--[[-- This module provides a way to display book information (filename and book metadata) ]] local BD = require("ui/bidi") local DocSettings = require("docsettings") local DocumentRegistry = require("document/documentregistry") local ImageViewer = require("ui/widget/imageviewer") local InfoMessage = require("ui/widget/infomessage") local KeyValuePage = require("ui/widget/keyvaluepage") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local filemanagerutil = require("apps/filemanager/filemanagerutil") local lfs = require("libs/libkoreader-lfs") local util = require("util") local _ = require("gettext") local BookInfo = WidgetContainer:extend{ bookinfo_menu_title = _("Book information"), } function BookInfo:init() if self.ui then -- only for Reader menu self.ui.menu:registerToMainMenu(self) end end function BookInfo:addToMainMenu(menu_items) menu_items.book_info = { text = self.bookinfo_menu_title, callback = function() self:onShowBookInfo() end, } end function BookInfo:isSupported(file) return lfs.attributes(file, "mode") == "file" end function BookInfo:show(file, book_props) local kv_pairs = {} local directory, filename = util.splitFilePathName(file) local filename_without_suffix, filetype = util.splitFileNameSuffix(filename) -- luacheck: no unused if filetype:lower() == "zip" then local filename_without_sub_suffix, sub_filetype = util.splitFileNameSuffix(filename_without_suffix) -- luacheck: no unused sub_filetype = sub_filetype:lower() local supported_sub_filetypes = { "fb2", "htm", "html", "log", "md", "txt" } for __, t in ipairs(supported_sub_filetypes) do if sub_filetype == t then filetype = sub_filetype .. "." .. filetype break end end end local file_size = lfs.attributes(file, "size") or 0 local file_modification = lfs.attributes(file, "modification") or 0 local size_f = util.getFriendlySize(file_size) local size_b = util.getFormattedSize(file_size) local size = string.format("%s (%s bytes)", size_f, size_b) table.insert(kv_pairs, { _("Filename:"), BD.filename(filename) }) table.insert(kv_pairs, { _("Format:"), filetype:upper() }) table.insert(kv_pairs, { _("Size:"), size }) table.insert(kv_pairs, { _("File date:"), os.date("%Y-%m-%d %H:%M:%S", file_modification) }) table.insert(kv_pairs, { _("Folder:"), BD.dirpath(filemanagerutil.abbreviate(directory)), separator = true }) -- book_props may be provided if caller already has them available -- but it may lack "pages", that we may get from sidecar file if not book_props or not book_props.pages then -- check there is actually a sidecar file before calling DocSettings:open() -- that would create an empty sidecar directory if DocSettings:hasSidecarFile(file) then local doc_settings = DocSettings:open(file) if doc_settings then if not book_props then -- Files opened after 20170701 have a "doc_props" setting with -- complete metadata and "doc_pages" with accurate nb of pages book_props = doc_settings:readSetting("doc_props") end if not book_props then -- File last opened before 20170701 may have a "stats" setting. -- with partial metadata, or empty metadata if statistics plugin -- was not enabled when book was read (we can guess that from -- the fact that stats.page = 0) local stats = doc_settings:readSetting("stats") if stats and stats.pages ~= 0 then -- Let's use them as is (which was what was done before), even if -- incomplete, to avoid expensive book opening book_props = stats end end -- Files opened after 20170701 have an accurate "doc_pages" setting. local doc_pages = doc_settings:readSetting("doc_pages") if doc_pages and book_props then book_props.pages = doc_pages end end end end -- If still no book_props (book never opened or empty "stats"), open the -- document to get them if not book_props then local document = DocumentRegistry:openDocument(file) if document then local loaded = true local pages if document.loadDocument then -- CreDocument if not document:loadDocument(false) then -- load only metadata -- failed loading, calling other methods would segfault loaded = false end -- For CreDocument, we would need to call document:render() -- to get nb of pages, but the nb obtained by simply calling -- here document:getPageCount() is wrong, often 2 to 3 times -- the nb of pages we see when opening the document (may be -- some other cre settings should be applied before calling -- render() ?) else -- for all others than crengine, we seem to get an accurate nb of pages pages = document:getPageCount() end if loaded then book_props = document:getProps() book_props.pages = pages end document:close() end end -- If still no book_props, fall back to empty ones if not book_props then book_props = {} end local title = book_props.title if title == "" or title == nil then title = _("N/A") end table.insert(kv_pairs, { _("Title:"), BD.auto(title) }) local authors = book_props.authors if authors == "" or authors == nil then authors = _("N/A") elseif authors:find("\n") then -- BD auto isolate each author authors = util.splitToArray(authors, "\n") for i=1, #authors do authors[i] = BD.auto(authors[i]) end authors = table.concat(authors, "\n") else authors = BD.auto(authors) end table.insert(kv_pairs, { _("Authors:"), authors }) local series = book_props.series if series == "" or series == nil then series = _("N/A") else -- If we were fed a BookInfo book_props (e.g., covermenu), series index is in a separate field if book_props.series_index then -- Here, we're assured that series_index is a Lua number, so round integers are automatically displayed without decimals series = book_props.series .. " #" .. book_props.series_index else -- But here, if we have a plain doc_props series with an index, drop empty decimals from round integers. series = book_props.series:gsub("(#%d+)%.0+$", "%1") end end table.insert(kv_pairs, { _("Series:"), BD.auto(series) }) local pages = book_props.pages if pages == "" or pages == nil then pages = _("N/A") end table.insert(kv_pairs, { _("Pages:"), pages }) local language = book_props.language if language == "" or language == nil then language = _("N/A") end table.insert(kv_pairs, { _("Language:"), language }) local keywords = book_props.keywords if keywords == "" or keywords == nil then keywords = _("N/A") elseif keywords:find("\n") then -- BD auto isolate each keywords keywords = util.splitToArray(keywords, "\n") for i=1, #keywords do keywords[i] = BD.auto(keywords[i]) end keywords = table.concat(keywords, "\n") else keywords = BD.auto(keywords) end table.insert(kv_pairs, { _("Keywords:"), keywords }) local description = book_props.description if description == "" or description == nil then description = _("N/A") else -- Description may (often in EPUB, but not always) or may not (rarely -- in PDF) be HTML. description = util.htmlToPlainTextIfHtml(book_props.description) end -- (We don't BD wrap description: it may be multi-lines, and the value we set -- here may be viewed in a TextViewer that has auto_para_direction=true, which -- will show the right thing, that'd we rather not mess with BD wrapping.) table.insert(kv_pairs, { _("Description:"), description }) -- Cover image local viewCoverImage = function() local widget local document = DocumentRegistry:openDocument(file) if document then if document.loadDocument then -- CreDocument document:loadDocument(false) -- load only metadata end local cover_bb = document:getCoverPageImage() if cover_bb then widget = ImageViewer:new{ image = cover_bb, with_title_bar = false, fullscreen = true, } end document:close() end if not widget then widget = InfoMessage:new{ text = _("No cover image available"), } end UIManager:show(widget) end table.insert(kv_pairs, { _("Cover image:"), _("Tap to display"), callback=viewCoverImage }) -- Get a chance to have title, authors... rendered with alternate -- glyphs for the book language (e.g. japanese book in chinese UI) local values_lang = nil if book_props.language and book_props.language ~= "" then values_lang = book_props.language end local widget = KeyValuePage:new{ title = _("Book information"), value_overflow_align = "right", kv_pairs = kv_pairs, values_lang = values_lang, } UIManager:show(widget) end function BookInfo:onShowBookInfo() if not self.document then return end -- Get them directly from ReaderUI's doc_settings local doc_props = self.ui.doc_settings:readSetting("doc_props") -- Make a copy, so we don't add "pages" to the original doc_props -- that will be saved at some point by ReaderUI. local book_props = {} for k, v in pairs(doc_props) do book_props[k] = v end book_props.pages = self.ui.doc_settings:readSetting("doc_pages") self:show(self.document.file, book_props) end function BookInfo:onShowBookDescription() if not self.document then return end local description = self.document:getProps().description if description and description ~= "" then -- Description may (often in EPUB, but not always) or may not (rarely -- in PDF) be HTML. description = util.htmlToPlainTextIfHtml(description) local TextViewer = require("ui/widget/textviewer") UIManager:show(TextViewer:new{ title = _("Description:"), text = description, }) else UIManager:show(InfoMessage:new{ text = _("No book description available."), }) end end function BookInfo:onShowBookCover() if not self.document then return end local cover_bb = self.document:getCoverPageImage() if cover_bb then UIManager:show(ImageViewer:new{ image = cover_bb, with_title_bar = false, fullscreen = true, }) else UIManager:show(InfoMessage:new{ text = _("No cover image available."), }) end end return BookInfo