# -*- coding: utf-8 -*- # # Copyright 2013 whiteleaf. All rights reserved. # require "fileutils" require "stringio" require_relative "novelsetting" require_relative "inspector" require_relative "illustration" require_relative "loadconverter" require_relative "downloader" require_relative "template" require_relative "progressbar" require_relative "helper" require_relative "inventory" require_relative "html" class NovelConverter NOVEL_TEXT_TEMPLATE_NAME = "novel.txt" NOVEL_TEXT_TEMPLATE_NAME_FOR_IBUNKO = "ibunko_novel.txt" attr_reader :use_dakuten_font if Narou.already_init? @@site_settings = Downloader.load_settings end # # 指定の小説を整形・変換する # def self.convert(target, options = {}) options = { # default paraeters output_filename: nil, display_inspector: false, ignore_force: false, ignore_default: false }.merge(options) setting = NovelSetting.load(target, options[:ignore_force], options[:ignore_default]) if setting novel_converter = new(setting, options[:output_filename], options[:display_inspector]) return { converted_txt_path: novel_converter.convert_main, use_dakuten_font: novel_converter.use_dakuten_font } end nil end # # テキストファイルを整形・変換する # def self.convert_file(filename, options = {}) options = { # default parameters encoding: nil, output_filename: nil, display_inspector: false, ignore_force: false, ignore_default: false }.merge(options) output_filename = options[:output_filename] if output_filename archive_path = File.dirname(output_filename) + "/" else archive_path = File.dirname(filename) + "/" end setting = NovelSetting.create(archive_path, options[:ignore_force], options[:ignore_default]) setting.author = "" setting.title = File.basename(filename) novel_converter = new(setting, output_filename, options[:display_inspector]) text = open(filename, "r:BOM|UTF-8") { |fp| fp.read }.gsub("\r", "") if options[:encoding] text.force_encoding(options[:encoding]).encode!(Encoding::UTF_8) end { converted_txt_path: novel_converter.convert_main(text), use_dakuten_font: novel_converter.use_dakuten_font } end def self.stash_aozora_fonts_directory fonts_path = File.join(File.dirname(Narou.get_aozoraepub3_path), "template/OPS/fonts") return unless File.exist?(fonts_path) FileUtils.mv(fonts_path, fonts_path + "_hide") end def self.visible_aozora_fonts_directory fonts_path = File.join(File.dirname(Narou.get_aozoraepub3_path), "template/OPS/fonts") return unless File.exist?(fonts_path + "_hide") FileUtils.mv(fonts_path + "_hide", fonts_path) end # # AozoraEpub3でEPUBファイル作成 # # AozoraEpub3は.jarがあるところがカレントディレクトリじゃないとうまく動かない # MEMO: # 逆にカレントディレクトリにAozoraEpub3の必須ファイルを置いて手を加えることで、 # テンプレート等の差し替えが容易になる # # 返り値:正常終了 :success、エラー終了 :error、AozoraEpub3が見つからなかった nil # def self.txt_to_epub(filename, use_dakuten_font = false, dst_dir = nil, device = nil, verbose = false) abs_srcpath = File.expand_path(filename) src_dir = File.dirname(abs_srcpath) cover_option = "" # MEMO: 外部実行からだと -c FILENAME, -c 1 オプションはぬるぽが出て動かない cover_filename = get_cover_filename(src_dir) if cover_filename cover_option = "-c 0" # 先頭の挿絵を表紙として利用 end dst_option = "" if dst_dir dst_option = %!-dst "#{File.expand_path(dst_dir)}"! end ext_option = "" device_option = "" if device case device.name when "Kobo" ext_option = "-ext " + device.ebook_file_ext when "Kindle" device_option = "-device kindle" end end pwd = Dir.pwd aozoraepub3_path = Narou.get_aozoraepub3_path unless aozoraepub3_path error "AozoraEpub3が見つからなかったのでEPUBが出力出来ませんでした。" + "narou initでAozoraEpub3の設定を行なって下さい" return nil end aozoraepub3_basename = File.basename(aozoraepub3_path) aozoraepub3_dir = File.dirname(aozoraepub3_path) java_encoding = "-Dfile.encoding=UTF-8" if Helper.os_cygwin? abs_srcpath = Helper.convert_to_windows_path(abs_srcpath) end Dir.chdir(aozoraepub3_dir) command = %!java #{java_encoding} -cp #{aozoraepub3_basename} AozoraEpub3 -enc UTF-8 -of #{device_option} ! + %!#{cover_option} #{dst_option} #{ext_option} "#{abs_srcpath}"! if Helper.os_windows? command = "cmd /c " + command.encode(Encoding::Windows_31J) end stash_aozora_fonts_directory unless use_dakuten_font print "AozoraEpub3でEPUBに変換しています" begin res = Helper::AsyncCommand.exec(command) do print "." end ensure visible_aozora_fonts_directory unless use_dakuten_font Dir.chdir(pwd) end # AozoraEpub3はエラーだとしてもexitコードは0なので、 # 失敗した場合はjavaが実行できない場合と確定できる unless res[2].success? puts puts res error "JavaがインストールされていないかAozoraEpub3実行時にエラーが発生しました。EPUBを作成出来ませんでした" return :error end stdout_capture = res[0] # Javaの実行環境に由来するであろうエラー if stdout_capture =~ /Error occurred during initialization of VM/ puts warn stdout_capture.strip warn "-" * 70 error "Javaの実行エラーが発生しました。EPUBを作成出来ませんでした\n" \ "Hint: 複数のJava環境が混じっていると起きやすいエラーのようです" return :error end if verbose puts puts "==== AozoraEpub3 stdout capture " + "=" * 47 puts stdout_capture.strip puts "=" * 79 end error_list = stdout_capture.scan(/^(?:\[ERROR\]|エラーが発生しました :).+$/) warn_list = stdout_capture.scan(/^\[WARN\].+$/) info_list = stdout_capture.scan(/^\[INFO\].+$/) if !error_list.empty? || !warn_list.empty? || !info_list.empty? puts puts error_list, warn_list, info_list unless error_list.empty? # AozoraEpub3 のエラーにはEPUBが出力されないエラーとEPUBが出力されるエラーの2種類ある。 # EPUBが出力される場合は「変換完了」という文字があるのでそれを検出する if stdout_capture !~ /^変換完了/ error "AozoraEpub3実行中にエラーが発生したため、EPUBが出力出来ませんでした" return :error end end end puts "変換しました" :success end # # EPUBファイルをkindlegenでMOBIへ # AozoraEpub3.jar と同じ場所に kindlegen が無ければ何もしない # # 返り値:正常終了 :success、エラー終了 :error、中断終了 :abort、kindlegenがなかった nil # def self.epub_to_mobi(epub_path, verbose = false) kindlegen_path = File.join(File.dirname(Narou.get_aozoraepub3_path), "kindlegen") if Dir.glob(kindlegen_path + "*").empty? error "kindlegenが見つかりませんでした。AozoraEpub3と同じディレクトリにインストールして下さい" return :error end if Helper.os_cygwin? epub_path = Helper.convert_to_windows_path(epub_path) end command = %!"#{kindlegen_path}" -locale ja "#{epub_path}"! if Helper.os_windows? command.encode!(Encoding::Windows_31J) end print "kindlegen実行中" res = Helper::AsyncCommand.exec(command) do print "." end stdout_capture, _, proccess_status = res stdout_capture.force_encoding(Encoding::UTF_8) if verbose puts puts "==== kindlegen stdout capture " + "=" * 49 puts stdout_capture.gsub("\n\n", "\n").strip puts "=" * 79 end if proccess_status.exited? if proccess_status.exitstatus == 2 puts "" error "kindlegen実行中にエラーが発生したため、MOBIが出力出来ませんでした" if stdout_capture.scan(/(エラー\(.+?\):\w+?:.+)$/) error $1 end return :error end else puts "" error "kindlegenが中断させられたぽいのでMOBIは出力出来ませんでした" return :abort end puts "変換しました" :success end def initialize(setting, output_filename = nil, display_inspector = false) @setting = setting @novel_id = setting.id @novel_author = setting.author @novel_title = setting.title @output_filename = output_filename @inspector = Inspector.new(@setting) @illustration = Illustration.new(@setting, @inspector) @display_inspector = display_inspector @use_dakuten_font = false end def load_novel_section(subtitle_info) file_subtitle = subtitle_info["file_subtitle"] || subtitle_info["subtitle"] # 互換性維持のため path = File.join(@section_save_dir, "#{subtitle_info["index"]} #{file_subtitle}.yaml") YAML.load_file(path) end def create_novel_text_by_template(sections) toc = @toc cover_chuki = @cover_chuki device = Narou.get_device setting = @setting processed_title = toc["title"] data = Database.instance.get_data("id", @novel_id) # タイトルに新着更新日を付加する if @setting.enable_add_date_to_title new_arrivals_date = data["new_arrivals_date"] || data["last_update"] date_str = new_arrivals_date.strftime(@setting.title_date_format) if @setting.title_date_align == "left" processed_title = date_str + processed_title else # right processed_title += date_str end end # タイトルに完結したかどうかを付加する tags = data["tags"] || [] if tags.include?("end") processed_title += " (完結)" end # タイトルがルビ化されてしまうのを抑制 processed_title = processed_title.gsub("《", "※[#始め二重山括弧]") .gsub("》", "※[#終わり二重山括弧]") tempalte_name = (device && device.ibunko? ? NOVEL_TEXT_TEMPLATE_NAME_FOR_IBUNKO : NOVEL_TEXT_TEMPLATE_NAME) Template.get(tempalte_name, binding, 1.0) end # # 表紙用の画像名取得 # def self.get_cover_filename(archive_path) [".jpg", ".png", ".jpeg"].each do |ext| filename = "cover#{ext}" cover_path = File.join(archive_path, filename) if File.exist?(cover_path) return filename end end nil end # # 表紙用挿絵注記作成 # def create_cover_chuki cover_filename = self.class.get_cover_filename(@setting.archive_path) if cover_filename "[#挿絵(#{cover_filename})入る]" else "" end end def find_site_setting @@site_settings.find { |s| s.multi_match(@toc["toc_url"], "url") } end # # 変換処理メイン # def convert_main(text = nil) print "ID:#{@novel_id} " if @novel_id puts "#{@novel_title} の変換を開始" sections = [] @cover_chuki = create_cover_chuki conv = load_converter(@novel_title, @setting.archive_path).new(@setting, @inspector, @illustration) if text result = conv.convert(text, "textfile") unless @setting.enable_enchant_midashi @inspector.info "テキストファイルの処理を実行しましたが、改行直後の見出し付与は有効になっていません。" + "setting.ini の enable_enchant_midashi を true にすることをお薦めします。" end splited = result.split("\n", 3) result = [splited[0], splited[1], @cover_chuki, splited[2]].join("\n") # 表紙の挿絵注記を3行目に挟み込む else @section_save_dir = Downloader.get_novel_section_save_dir(@setting.archive_path) @toc = Downloader.get_toc_data(@setting.archive_path) @toc["story"] = conv.convert(@toc["story"], "story") html = HTML.new site_setting = find_site_setting html.set_illust_setting({current_url: site_setting["illust_current_url"], grep_pattern: site_setting["illust_grep_pattern"]}) progressbar = ProgressBar.new(@toc["subtitles"].size) @toc["subtitles"].each_with_index do |subinfo, i| progressbar.output(i) section = load_novel_section(subinfo) if section["chapter"].length > 0 section["chapter"] = conv.convert(section["chapter"], "chapter") end @inspector.subtitle = section["subtitle"] element = section["element"] data_type = element.delete("data_type") || "text" element.each do |text_type, elm_text| if data_type == "html" html.string = elm_text elm_text = html.to_aozora end element[text_type] = conv.convert(elm_text, text_type) end section["subtitle"] = conv.convert(section["subtitle"], "subtitle") sections << section end progressbar.clear result = create_novel_text_by_template(sections) end @use_dakuten_font = conv.use_dakuten_font inspect_novel(result) if @output_filename save_path = File.join(@setting.archive_path, File.basename(@output_filename)) else if text info = get_title_and_author_by_text(result) else info = { "author" => @novel_author, "title" => @novel_title } end save_filename = Narou.create_novel_filename(info) save_path = File.join(@setting.archive_path, save_filename) if save_path !~ /\.\w+$/ save_path += ".txt" end end File.write(save_path, result) puts "縦書用の変換が終了しました" update_latest_convert_novel save_path end # # テキストデータ先頭二行からタイトルと作者名を取得 # def get_title_and_author_by_text(text) title, author = text.split("\n", 3) { "title" => title, "author" => author } end def inspect_novel(text) # 行末読点の現在状況を調査する @inspector.inspect_end_touten_conditions(text) @inspector.countup_return_in_brackets(text) if !@display_inspector unless @inspector.empty? puts "小説状態の調査結果を #{Inspector::INSPECT_LOG_NAME} に出力しました" end else # 小説の監視・検査状況を表示する if @inspector.error? || @inspector.warning? puts "―――― 小説にエラーもしくは警告が存在します ――――".termcolor puts "" @inspector.display(Inspector::ERROR | Inspector::WARNING) puts "" end if @inspector.info? puts "―――― 小説の検査状況を表示します ――――".termcolor puts "" @inspector.display(Inspector::INFO) puts "" end end @inspector.save end # # 最近変換した小説IDを記録更新 # def update_latest_convert_novel id = Downloader.get_id_by_target(@novel_title) Inventory.load("latest_convert", :local).tap { |inv| inv["id"] = id inv.save } end end