def get_executable_name():
    if getattr(sys, 'frozen', False):
        name, _ = os.path.splitext(os.path.basename((sys.executable)))
    else:
        name, _ = os.path.splitext(os.path.basename((sys.argv[0])))

    return name def get_executable_path():
    if getattr(sys, 'frozen', False):
        executable_path = os.path.abspath(os.path.dirname(sys.executable))
    else:
        executable_path = os.path.abspath(os.path.dirname(sys.argv[0]))

    return executable_path def format_pattern(s, seq):

    def replace_keyword(pict, k, v):
        if pict.count(k) > 0:
            return pict.replace(k, v), True if v else False
        return pict, False

    def replace_keywords(pict, seq):
        expanded = False
        for (k, v) in seq:
            pict, ok = replace_keyword(pict, k, v)
            expanded = expanded or ok
        if not expanded:
            return ''
        return pict

    p_o = -1
    p_c = -1

    # Hack - I do not want to write real parser
    pps = s.replace(r'\{', chr(1)).replace(r'\}', chr(2))

    for i, sym in enumerate(pps):
        if sym == '{':
            p_o = i
        elif sym == '}':
            p_c = i
            break

    if p_o >= 0 and p_c > 0 and p_o < p_c:
        pps = format_pattern(pps[0:p_o] + replace_keywords(pps[p_o + 1:p_c], seq) + pps[p_c + 1:], seq)
    else:
        pps = replace_keywords(pps, seq)

    return pps.replace(chr(1), '{').replace(chr(2), '}') def clean_file_name(fname):

    # Just in case - control path separators
    fname = fname.replace(os.sep, '')
    fname = fname.translate(other_reserved)

    if version.WINDOWS:
        # Just in case
        fname = fname.translate(windows_reserved)
        # Seriously, not sure very long names are practical anyways
        # I know that this is Windows only, on newer versions we probably could set "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled 1" and remove this line
        fname = smart_truncate(fname, max_length=240)

    return fname class Fb2XHTML:

    def __init__(self, fb2file, tempdir, config):

        self.log = config.log

        self.kindle = config.output_format.lower() in ('mobi', 'azw3')

        self.buff = []
        self.current_header_level = 0  # Уровень текущего заголовка
        self.header = False  # Признак формирования заголовка
        self.subheader = False  # Признак формирования подзаголовка
        self.first_chapter_line = False  # Признак первой строки в главе (секции) - для расстановки dropcaps
        self.inline_image_mode = False  # Индикатор режима вставки картинок (inline)
        self.body_name = ''  # Имя текущего раздела body, например notes
        self.no_paragraph = False  # Индикатор, что последующий парагаф находится в эпиграфе, аннотации и т.п.
        self.first_header_in_body = True  # Признак первого заголовка в секции body

        # Make sure book title is never empty
        temp_book_name = os.path.basename(fb2file)
        if not temp_book_name:
            temp_book_name = fb2file
        if os.path.splitext(temp_book_name)[1].lower() == '.fb2':
            temp_book_name = os.path.splitext(temp_book_name)[0]

        self.orig_file_name = fb2file

        self.book_title = temp_book_name  # Название книги
        self.book_authors = []  # Author(s)
        self.book_lang = 'ru'  # Язык книги, по-умолчанию 'ru'
        self.book_series = ''  # Книжная серия
        self.book_series_num = ''  # Номер в книжной серии
        self.book_cover = ''  # Ссылка на файл изображения обложки книги
        self.book_date = ''

        self.dropcaps = config.current_profile['dropcaps'].lower()  # Признак вставки стилей буквицы (dropcaps)
        self.nodropcaps = config.no_dropcaps_symbols  # Строка символов, для исключения буквицы

        # Максимальный уровень заголовка (секции) для помещения в содержание (toc.xhtml)
        # В toc.ncx помещаются все уровни
        self.toc_max_level = config.current_profile['tocMaxLevel'] if config.current_profile['tocMaxLevel'] else 1000
        # How to split toc.ncx for Kindle (eInk devices only show 2 levels)
        self.toc_kindle_level = config.current_profile['tocKindleLevel'] if config.current_profile['tocKindleLevel'] else 2

        self.authorformat = config.current_profile['authorFormat']
        self.booktitleformat = config.current_profile['bookTitleFormat']

        self.css_file = config.current_profile['css']
        self.parse_css = config.current_profile['parse_css']

        self.open_book_from_cover = config.current_profile['openBookFromCover']
        if self.open_book_from_cover and self.kindle:
            self.open_book_from_cover = False
            self.log.warning('For "kindle" books (mobi, azw3) option "open book from cover" is ignored')

        self.annotation = None

        self.generate_toc_page = config.current_profile['generateTOCPage']
        self.generate_annotation_page = config.current_profile['generateAnnotationPage']
        self.generate_opf_guide = config.current_profile['generateOPFGuide']

        self.vignettes = config.current_profile['vignettes']
        self.vignette_files = []

        self.removepngtransparency = config.current_profile['removePngTransparency']  # Remove transparency in PNG images

        self.annotation_title = config.current_profile['annotationTitle']  # Заголовок для раздела аннотации
        self.toc_title = config.current_profile['tocTitle']  # Заголовок для раздела содержания

        self.chaptersplit = config.current_profile['chapterOnNewPage']  # Разделять на отдельные файлы по главам
        self.chapterlevel = config.current_profile['chapterLevel']

        self.seriespositions = config.current_profile['seriesPositions']

        self.tocbeforebody = config.current_profile['tocBeforeBody']  # Положение содержания - в начале либо в конце книги
        self.transliterate_author_and_title = config.transliterate_author_and_title

        self.screen_width = config.screen_width
        self.screen_height = config.screen_height

        self.toc_type = config.current_profile['tocType']

        self.toc = {}  # Содрержание, формируется по мере парсинга
        self.toc_index = 1  # Текущий номер раздела содержания
        # Имя текущего файла для записи текста книги в xhtml.
        self.current_file = 'index.xhtml'
        self.current_file_index = 0

        # Для включения сносок и комментариев в текст книги
        self.notes_dict = {}  # Словарь со сносками и комментариями
        self.notes_order = []  # Notes in order of discovery
        self.notes_titles = {}  # Dictionary of note body titles
        self.notes_mode = config.current_profile['notesMode']  # Режим отображения сносок: inline, block
        self.notes_bodies = config.current_profile['notesBodies']
        self.current_notes = []  # Переменная для хранения текущей сноски

        self.temp_dir = tempdir  # Временный каталог для записи промежуточных файлов
        self.temp_content_dir = os.path.join(self.temp_dir, 'OEBPS')
        self.temp_inf_dir = os.path.join(self.temp_dir, 'META-INF')

        self.html_file_list = []  # Массив для хранения списка сгенерированных xhtml файлов
        self.image_file_list = []  # Массив для хранения списка картинок
        self.image_count = 0

        self.pages_list = {}  # Additional pages per file
        self.page_length = 0

        self.characters_per_page = config.characters_per_page

        # Additional cover processiing

        # when set to Top, Center or Bottom cover will be stamped
        self.cover_stamp = config.current_profile['coverStamp']
        # when book has no cover image this file will be used instead
        self.cover_default = config.current_profile['coverDefault']
        # if it ponts to font file and cover_stamp is not 'None' cover will be stamped
        self.cover_font = config.current_profile['coverTextFont']

        # if not 0 - scale all images but cover
        self.images_scale = config.current_profile['scaleImages']

        self.genres = []

        self.tree = etree.parse(fb2file, parser=etree.XMLParser(recover=True))
        if 'xslt' in config.current_profile:

            # rupor - this allows for smaller xsl, quicker replacement and allows handling of tags in the paragraphs
            class MyExtElement(etree.XSLTExtension):

                def execute(self, _, self_node, input_node, output_parent):
                    child = deepcopy(input_node)
                    found = False
                    for elem in child.getiterator():
                        if not found and elem.text is not None:
                            found = True
                            old_text = elem.text
                            elem.text = self_node.text
                            if len(old_text) > 1:
                                i = 1
                                for c in old_text[1:]:
                                    if c.isspace():
                                        i += 1
                                    else:
                                        break
                                elem.text = elem.text + old_text[i:]
                        if not hasattr(elem.tag, 'find'):
                            continue
                        i = elem.tag.find('}')
                        if i >= 0:
                            elem.tag = elem.tag[i + 1:]
                    objectify.deannotate(child, cleanup_namespaces=True)
                    output_parent.append(child)

            config.log.info('Applying XSLT transformations "{0}"'.format(config.current_profile['xslt']))
            self.transform = etree.XSLT(etree.parse(config.current_profile['xslt']), extensions={('fb2mobi_ns', 'katz_tr'): MyExtElement()})
            transformed = self.transform(self.tree)
            for entry in self.transform.error_log:
                self.log.warning(entry)

            # Make parser re-read XML - in case transformation changed document structure
            transformed_file_name = os.path.join(tempdir, os.path.split(fb2file)[1])
            transformed.write(transformed_file_name, encoding='utf-8', method='xml', xml_declaration=True, pretty_print=False)
            self.tree = etree.parse(transformed_file_name, parser=etree.XMLParser(recover=True))
            try:
                os.remove(transformed_file_name)
            except:
                config.log.info('Unable to remove transformed file "{0}".'.format(transformed_file_name))

        self.root = self.tree.getroot()

        self.hyphenate = config.current_profile['hyphens']
        if self.hyphenate:
            self.replaceNBSP = config.current_profile['hyphensReplaceNBSP']
            self.hyphenator = MyHyphen(self.book_lang)

        self.first_body = True  # Признак первого body
        self.font_list = []
        self.book_uuid = uuid.uuid4()
        self.links_location = {}

    def get_book_authors(self, short=False):
        if self.book_authors:
            if short and len(self.book_authors) > 1:
                return self.book_authors[0] + ' и др' if self.book_lang.lower() == 'ru' else ', et al'
            return ', '.join(self.book_authors)
        else:
            return ''

    def generate(self):

        self.get_notes_dict()

        for child in self.root:
            if ns_tag(child.tag) == 'binary':
                self.parse_binary(child)

        for child in self.root:
            if ns_tag(child.tag) == 'description':
                self.parse_description(child)
            elif ns_tag(child.tag) == 'body':
                self.parse_body(child)

        self.correct_links()
        self.correct_images()

        if self.generate_toc_page:
            self.generate_toc()
        self.generate_cover()
        self.generate_ncx()

        if self.css_file:
            self.copy_css()

        for v in self.vignette_files:
            try:
                copy_file(v, os.path.join(os.path.join(self.temp_content_dir, 'vignettes'), os.path.split(v)[1]))
            except:
                self.log.warning('File {} not found.'.format(v))

        self.generate_pagemap()
        self.generate_opf()
        self.generate_container()
        self.generate_mimetype()

    def copy_css(self):
        base_dir = os.path.abspath(os.path.dirname(self.css_file))
        self.font_list = []

        def replace_url(url):
            source_file = os.path.abspath(os.path.join(base_dir, url))

            if os.path.splitext(url)[1].lower() in ('.ttf', '.otf'):
                dest_file = os.path.abspath(os.path.join(self.temp_content_dir, 'fonts', os.path.basename(source_file)))
                new_url = 'fonts/' + os.path.basename(url)
                self.font_list.append(new_url)
            else:
                dest_file = os.path.abspath(os.path.join(self.temp_content_dir, 'images', 'css_' + os.path.basename(source_file)))
                new_url = 'images/css_' + os.path.basename(url)

            try:
                copy_file(source_file, dest_file)
            except:
                self.log.error('File {0}, referred by css, not found.'.format(url))

            return new_url

        if self.parse_css:

            # Note, macros are temporary, until CSS3 module is fixed and starts to recognize "rem" units
            cssutils.profile.addProfile('CSS extentions', {
                '-webkit-hyphens': 'none',
                'adobe-hyphenate': 'none',
                '-moz-hyphens': 'none',
                '-ms-hyphens': 'none',
                'hyphens': 'none|manual|auto'
            }, {
                'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc|q|ch|rem|vw|vh|vmin|vmax)',
                'positivelength': r'0|{positivenum}(em|ex|px|in|cm|mm|pt|pc|q|ch|rem|vw|vh|vmin|vmax)',
                'angle': r'0|{num}(deg|grad|rad|turn)'
            })

            stylesheet = cssutils.parseFile(self.css_file)
            cssutils.replaceUrls(stylesheet, replace_url)
            write_file(str(stylesheet.cssText, 'utf-8'), os.path.join(self.temp_content_dir, 'stylesheet.css'))
        else:
            copy_file(self.css_file, os.path.join(self.temp_content_dir, 'stylesheet.css'))

    def correct_links(self):
        for fl in self.html_file_list:
            parser = etree.XMLParser(encoding='utf-8')
            root = etree.parse(os.path.join(self.temp_content_dir, fl), parser).getroot()

            for elem in root.xpath('//xhtml:a', namespaces={'xhtml': 'http://www.w3.org/1999/xhtml'}):
                link = elem.get('href', '')
                if link and link.startswith('#'):
                    try:
                        elem.set('href', self.links_location[link[1:]] + link)
                    except:
                        pass

            self.buff = str.replace(str(etree.tostring(root, encoding='utf-8', method='xml', xml_declaration=True), 'utf-8'), ' encoding=\'utf-8\'', '', 1)

            self.current_file = fl
            self.write_buff()

    def correct_images(self):
        if self.book_cover:
            # Leave only first cover - drop the rest
            have_cover = False
            covers = []
            for imgid, imgtype, file in self.image_file_list:
                if imgid == self.book_cover:
                    if have_cover:
                        covers.append((imgid, imgtype, file))
                    else:
                        have_cover = True
            for item in covers:
                self.image_file_list.remove(item)
        elif self.kindle and self.cover_default:
            # no cover - if "kindle" provide basic dummy one
            try:
                dst_name = "cover{0:08}.jpg".format(self.image_count)
                copy_file(self.cover_default, os.path.join(os.path.join(self.temp_content_dir, 'images'), dst_name))
                self.image_file_list.append(("dummycover.jpg", "image/jpeg", dst_name))
                self.image_count += 1
                self.book_cover = "dummycover.jpg"
                # default cover will always be stamped
                if self.cover_stamp == 'None':
                    self.cover_stamp = 'Center'
            except:
                self.log.warning('Default cover {} not found.'.format(self.cover_default))

        if self.images_scale > 0.0:
            # if requested - resample all images but cover
            for imgid, _, file in self.image_file_list:
                if imgid != self.book_cover:
                    full_name = os.path.join(self.temp_content_dir, 'images', file)
                    temp_name = os.path.join(self.temp_content_dir, 'images', "image.tmp")
                    try:
                        im = Image.open(full_name)

                        imgfmt = im.format
                        if not imgfmt.lower() in ('jpeg', 'png'):
                            continue

                        dpi = im.info.get("dpi")
                        if not dpi:
                            dpi = (300, 300)

                        im = im.resize((int(im.width * self.images_scale), int(im.height * self.images_scale)), Image.LANCZOS)
                        im.save(temp_name, format=imgfmt, dpi=dpi)
                        os.replace(temp_name, full_name)
                    except:
                        os.remove(temp_name)
                        self.log.warning('Unable to resample image {}. Skipping...'.format(file))

    def write_buff(self, dname='', fname=''):
        if not fname:
            dirname = self.temp_content_dir
            filename = os.path.join(self.temp_content_dir, self.current_file)
        else:
            dirname = dname
            filename = os.path.join(dname, fname)

        if not os.path.exists(dirname):
            os.makedirs(dirname)

        parser = etree.XMLParser(encoding='utf-8', remove_blank_text=True)
        xhtml = etree.parse(io.StringIO(self.get_buff()), parser)
        xhtml.write(filename, encoding='utf-8', method='xml', xml_declaration=True, pretty_print=True)

    # def write_buff_debug(self, dname='', fname=''):
    #     if len(fname) == 0:
    #         dirname = self.temp_content_dir
    #         filename = os.path.join(self.temp_content_dir, self.current_file)
    #     else:
    #         dirname = dname
    #         filename = os.path.join(dname, fname)
    #     write_file(self.get_buff(), filename)

    def write_debug(self, dname):
        self.tree.write(os.path.join(dname, os.path.split(self.orig_file_name)[1]), encoding='utf-8', method='xml', xml_declaration=True, pretty_print=False)

    def parse_note_elem(self, elem, body_name):
        note_title = ''

        if ns_tag(elem.tag) == 'title':

            # this is essetially a hack to preserve notes title (if any) for floating notes

            toc_title = etree.tostring(elem, method='text', encoding='utf-8').decode('utf-8').strip()
            toc_title = re.compile(r'[\[{].*[\]}]').sub('', toc_title)  # Удалим остатки ссылок
            if toc_title:
                # Do real title parsing (notes file is not in pages_list anyways)
                save_buff = self.buff
                self.buff = []
                self.header = True
                self.parse_format(elem, 'div', 'h0')
                self.header = False
                self.notes_titles[body_name] = (toc_title, self.buff[:])
                self.buff = save_buff

        elif ns_tag(elem.tag) == 'section' and 'id' in elem.attrib:
            elid = elem.attrib['id']
            notetext = []
            self.buff = []

            for e in elem:
                if ns_tag(e.tag) == 'title':
                    note_title = etree.tostring(e, method='text', encoding='utf-8').decode('utf-8').strip()
                else:
                    notetext.append(etree.tostring(e, method='text', encoding='utf-8').decode('utf-8').strip())

            self.notes_dict[elid] = (note_title, ' '.join(notetext))
            self.notes_order.append((elid, body_name))
        else:
            for e in elem:
                self.parse_note_elem(e, body_name)

    def get_notes_dict(self):
        self.notes_dict = {}

        notes_bodies = self.notes_bodies.replace(' ', '').split(',')

        for item in self.root:
            if ns_tag(item.tag) == 'body':
                if 'name' in item.attrib:
                    if item.attrib['name'] in notes_bodies:
                        for section in item:
                            self.parse_note_elem(section, item.attrib['name'])

    def get_vignette(self, level, vignette_type):
        vignette = None
        try:
            vignette = self.vignettes[level][vignette_type]
        except:
            try:
                vignette = self.vignettes['default'][vignette_type]
            except:
                pass

        found = False

        if vignette:
            for v in self.vignette_files:
                if v == vignette:
                    found = True
                    break

            if not found:
                self.vignette_files.append(vignette)

        if vignette:
            vignette = os.path.split(vignette)[1]

        return vignette

    def parse_description(self, elem):
        lastname = ''
        middlename = ''
        firstname = ''

        self.log.debug('Parsing description')

        for e in elem:
            if ns_tag(e.tag) == 'document-info':
                for t in e:
                    if ns_tag(t.tag) == 'id':
                        if t.text:
                            try:
                                self.book_uuid = uuid.UUID(t.text)
                            except:
                                pass
                            break
            elif ns_tag(e.tag) == 'title-info':
                for t in e:
                    if ns_tag(t.tag) == 'book-title':
                        if t.text:
                            self.book_title = t.text
                    elif ns_tag(t.tag) == 'lang':
                        if t.text:
                            self.book_lang = t.text if len(t.text) > 2 else t.text.lower()
                        else:
                            self.book_lang = 'ru'

                        if self.book_lang in 'rus':
                            self.book_lang = 'ru'

                        if self.hyphenate and self.hyphenator:
                            try:
                                self.hyphenator.set_language(self.book_lang.replace("-", "_"))
                            except:
                                self.log.warning('Unable to set hyphenation dictionary for language code "{}" - turning hyphenation off'.format(self.book_lang))
                                self.hyphenate = False

                    elif ns_tag(t.tag) == 'coverpage':
                        for c in t:
                            if ns_tag(c.tag) == 'image':
                                for a in c.attrib:
                                    if ns_tag(a) == 'href':
                                        self.book_cover = c.attrib[a][1:]
                                        break
                                    elif ':href' in a:
                                        self.book_cover = c.attrib[a][1:]
                                        self.log.warning('Wrong namespace is used for href attribute for cover page: {0}. Will attempt to recover...'.format(c.attrib))
                                        break

                    elif ns_tag(t.tag) == 'genre':
                        self.genres.append(t.text)

                    elif ns_tag(t.tag) == 'author':

                        for a in t:
                            if ns_tag(a.tag) == 'first-name':
                                firstname = a.text
                            elif ns_tag(a.tag) == 'middle-name':
                                middlename = a.text
                            elif ns_tag(a.tag) == 'last-name':
                                lastname = a.text

                        # pylint: disable=C0330
                        # yapf: disable
                        ba = format_pattern(self.authorformat,
                            [
                                ('#fi', '' if not firstname else firstname[0] + '.'),
                                ('#mi', '' if not middlename else middlename[0] + '.'),
                                ('#f', '' if not firstname else firstname.strip()),
                                ('#m', '' if not middlename else middlename.strip()),
                                ('#l', '' if not lastname else lastname.strip())
                            ])
                        # yapf: enable

                        self.book_authors.append(ba.strip())

                    elif ns_tag(t.tag) == 'sequence':
                        if 'name' in t.attrib:
                            self.book_series = t.attrib['name']
                        if 'number' in t.attrib:
                            self.book_series_num = t.attrib['number']

                    elif ns_tag(t.tag) == 'annotation':
                        self.annotation = etree.tostring(t, method='text', encoding='utf-8').decode('utf-8').strip()

                        if self.generate_annotation_page:
                            self.buff = []
                            self.current_file = 'annotation.xhtml'
                            self.html_file_list.append(self.current_file)

                            self.buff.append(HTMLHEAD)
                            self.buff.append('<div class="annotation"><div class="h1">{0}</div>'.format(self.annotation_title))
                            self.parse_format(t, 'div')
                            self.buff.append('</div>')
                            self.buff.append(HTMLFOOT)

                            self.write_buff()

                    elif ns_tag(t.tag) == 'date':
                        self.book_date = etree.tostring(t, method='text', encoding='utf-8').decode('utf-8').strip()

    def parse_binary(self, elem):
        if 'id' in elem.attrib:
            self.log.debug('Parsing binary {0}'.format(elem.attrib))
            have_file = False
            elid = elem.attrib['id']
            decl_type = elem.attrib['content-type'].lower() if 'content-type' in elem.attrib else '---empty---'
            buff = base64.b64decode(elem.text.encode('ascii'))
            try:
                img = Image.open(io.BytesIO(buff))
                real_type = Image.MIME[img.format]
                imgfmt = img.format.lower()
                filename = "bin{0:08}.{1}".format(self.image_count, imgfmt.lower().replace('jpeg', 'jpg'))
                full_name = os.path.join(os.path.join(self.temp_content_dir, 'images'), filename)
                make_dir(full_name)

                if self.kindle and not imgfmt in ('gif', 'jpeg', 'png', 'bmp'):
                    self.log.warning('Image type "{0}" for ref-id "{1} is not supported by your device. Ignoring...'.format(real_type, elid))
                    return

                if real_type != decl_type:
                    self.log.warning('Declared and detected image types for ref-id "{0}" do not match: "{1}" is not "{2}". Using detected type...'.format(elid, decl_type, real_type))

                if self.removepngtransparency and imgfmt == 'png' and (img.mode in ('RGBA', 'LA') or (img.mode in ('RGB', 'L', 'P') and 'transparency' in img.info)):
                    try:
                        self.log.debug('Removing image transparency for ref-id "{0}" in file "{1}"'.format(elid, filename))
                        if img.mode == "P" and isinstance(img.info.get("transparency"), bytes):
                            img = img.convert("RGBA")
                        if img.mode in ("L", "LA"):
                            bg = Image.new("L", img.size, 255)
                        else:
                            bg = Image.new("RGB", img.size, (255, 255, 255))
                        alpha = img.convert("RGBA").split()[-1]
                        bg.paste(img, mask=alpha)
                        bg.save(full_name, dpi=img.info.get("dpi"))
                        have_file = True
                    except:
                        self.log.warning('Unable to remove transparency for ref-id "{0}" in file "{1}"'.format(elid, filename))
                        self.log.debug('Getting details:', exc_info=True)

                self.image_count += 1

            except:
                # Pillow does not recognize SVG files
                if decl_type.split('/')[1].lower() == 'svg':
                    real_type = 'image/svg+xml'
                    filename = "bin{0:08}.svg".format(self.image_count)
                    full_name = os.path.join(os.path.join(self.temp_content_dir, 'images'), filename)
                    self.image_count += 1
                else:
                    self.log.error('Unable to process binary for ref-id "{0}". Skipping...'.format(elid))
                    # self.log.debug('Getting details:', exc_info=True)
                    return

            if not have_file:
                write_file_bin(buff, full_name)

            self.image_file_list.append((elid, real_type, filename))

    def parse_span(self, span, elem):
        self.parse_format(elem, 'span', span)

    def parse_emphasis(self, elem):
        self.parse_span('emphasis', elem)

    def parse_strong(self, elem):
        self.parse_span('strong', elem)

    def parse_strikethrough(self, elem):
        self.parse_span('strike', elem)

    def parse_style(self, elem):
        self.parse_format(elem, 'span')

    def parse_emptyline(self):
        self.buff.append('<div class="emptyline" />')

    def parse_title(self, elem):
        toc_ref_id = 'tocref{0}'.format(self.toc_index)
        toc_title = etree.tostring(elem, method='text', encoding='utf-8').decode('utf-8').strip()
        toc_title = re.compile(r'[\[{].*[\]}]').sub('', toc_title)  # Удалим остатки ссылок

        if not self.body_name or self.first_header_in_body:
            self.header = True
            self.first_chapter_line = True

            if self.current_header_level < self.chapterlevel:
                self.buff.append('<div class="titleblock" id="{0}">'.format(toc_ref_id))
            else:
                self.buff.append('<div class="titleblock_nobreak" id="{0}">'.format(toc_ref_id))

            if not self.body_name and self.first_header_in_body:
                vignette = self.get_vignette('h0', 'beforeTitle')
                if vignette:
                    self.buff.append('<div class="vignette_title_before"><img src="vignettes/{0}" /></div>'.format(vignette))

                self.parse_format(elem, 'div', 'h0')

                vignette = self.get_vignette('h0', 'afterTitle')
                if vignette:
                    self.buff.append('<div class="vignette_title_after"><img src="vignettes/{0}" /></div>'.format(vignette))

            else:
                level = 'h{0}'.format(self.current_header_level if self.current_header_level <= 6 else 6)

                vignette = self.get_vignette(level, 'beforeTitle')
                if vignette:
                    self.buff.append('<div class="vignette_title_before"><img src="vignettes/{0}" /></div>'.format(vignette))

                self.parse_format(elem, 'div', level)

                vignette = self.get_vignette(level, 'afterTitle')
                if vignette:
                    self.buff.append('<div class="vignette_title_after"><img src="vignettes/{0}" /></div>'.format(vignette))

            self.toc[self.toc_index] = ['{0}#{1}'.format(self.current_file, toc_ref_id), toc_title, self.current_header_level, self.body_name]
        else:
            self.buff.append('<div class="titlenotes" id="{0}">'.format(toc_ref_id))
            self.parse_format(elem, 'div')

        self.buff.append('</div>')
        self.first_header_in_body = False
        self.toc_index += 1
        self.header = False

    def parse_subtitle(self, elem):
        self.subheader = True
        self.parse_format(elem, 'p', 'subtitle')
        self.subheader = False

    def parse_image(self, elem):
        img_id = None
        int_id = None
        alt = None

        for a in elem.attrib:
            if ns_tag(a) == 'href':
                int_id = elem.attrib[a][1:]
            elif ':href' in a:
                self.log.warning('Wrong namespace is used for href attribute in <image>: {0}. Will attempt to recover...'.format(elem.attrib))
                int_id = elem.attrib[a][1:]
            elif ns_tag(a) == 'id':
                img_id = elem.attrib[a]
            elif ns_tag(a) == 'alt':
                alt = elem.attrib[a]

        if not int_id:
            self.log.error('Unable to find image ref-id in "{0}" "{1}.'.format(elem.tag, elem.attrib))
            return

        filename = None
        for imgid, _, file in self.image_file_list:
            if imgid == int_id:
                filename = file
                break
        if not filename:
            self.log.error('Unable to find image for ref-id "{0}" in "{1}" "{2}.'.format(int_id, elem.tag, elem.attrib))
            filename = "nonexistent.gif"
            alt = int_id

        if not alt:
            alt = filename

        if self.inline_image_mode:
            if img_id:
                self.buff.append('<img id="{0}" class="inlineimage" src="images/{1}" alt="{2}"/>'.format(img_id, filename, alt))
            else:
                self.buff.append('<img class="inlineimage" src="images/{0}" alt="{1}"/>'.format(filename, alt))
        else:
            if img_id:
                self.buff.append('<div id="{0}" class="image">'.format(img_id))
            else:
                self.buff.append('<div class="image">')
            self.buff.append('<img src="images/{0}" alt="{1}"/>'.format(filename, alt))
            self.buff.append('</div>')

        self.parse_format(elem)

    def parse_a(self, elem):
        href = None
        for name in elem.attrib:
            if ns_tag(name) == 'href':
                href = elem.attrib[name]
                break
            elif ':href' in name:
                self.log.warning('Wrong namespace is used for href attribute in <a>: {0}. Will attempt to recover...'.format(elem.attrib))
                href = elem.attrib[name]
                break
        if not href:
            self.log.error('Unable to find href attribute in <a>: {0}.'.format(elem.attrib))

        self.parse_format(elem, 'a', 'anchor', href=href)

    def parse_p(self, elem):
        ptag = 'p'
        pcss = None

        if self.header:
            pcss = 'title'

        self.parse_format(elem, ptag, pcss)

    def parse_poem(self, elem):
        self.no_paragraph = True
        self.parse_format(elem, 'div', 'poem')
        self.no_paragraph = False

    def parse_stanza(self, elem):
        self.parse_format(elem, 'div', 'stanza')

    def parse_v(self, elem):
        self.parse_format(elem, 'p')

    def parse_cite(self, elem):
        self.parse_format(elem, 'div', 'cite')

    def parse_textauthor(self, elem):
        self.no_paragraph = True
        self.parse_format(elem, 'div', 'text-author')
        self.no_paragraph = False

    def parse_annotation(self, elem):
        self.no_paragraph = True
        self.parse_format(elem, 'div', 'annotation')
        self.no_paragraph = False

    def parse_table(self, elem):
        self.buff.append('<table class="table"')
        for attr in elem.attrib:
            self.buff.append(' {0}="{1}"'.format(attr, elem.attrib[attr]))
        self.buff.append('>')
        self.parse_format(elem)
        self.buff.append('</table>')

    def parse_epigraph(self, elem):
        self.no_paragraph = True
        self.parse_format(elem, 'div', 'epigraph')
        self.no_paragraph = False

    def parse_code(self, elem):
        self.parse_format(elem, 'code')

    def parse_other(self, elem):
        self.parse_format(elem, ns_tag(elem.tag))

    def parse_section(self, elem):

        if not self.body_name and self.current_header_level == 0 and self.first_header_in_body:

            # We encountered main body without a title - need to add it forcefully, otherwise toc and books structure would be wrong

            toc_ref_id = 'tocref{0}'.format(self.toc_index)
            if len(self.book_authors) == 1:
                toc_title = self.book_authors[0] + " " + self.book_title
            else:
                toc_title = self.book_title

            if self.current_header_level < self.chapterlevel:
                self.buff.append('<div class="titleblock" id="{0}">'.format(toc_ref_id))
            else:
                self.buff.append('<div class="titleblock_nobreak" id="{0}">'.format(toc_ref_id))

            vignette = self.get_vignette('h0', 'beforeTitle')
            if vignette:
                self.buff.append('<div class="vignette_title_before"><img src="vignettes/{0}" /></div>'.format(vignette))

            self.buff.append('<div class ="h0">')
            for a in self.book_authors:
                self.buff.append('<p class="title">{0}</p>'.format(a))
            self.buff.append('<p class="title">{0}</p>'.format(self.book_title))
            self.buff.append('</div>')

            vignette = self.get_vignette('h0', 'afterTitle')
            if vignette:
                self.buff.append('<div class="vignette_title_after"><img src="vignettes/{0}" /></div>'.format(vignette))

            self.toc[self.toc_index] = ['{0}#{1}'.format(self.current_file, toc_ref_id), toc_title, self.current_header_level, '']

            self.buff.append('</div>')
            self.first_header_in_body = False
            self.toc_index += 1

        self.current_header_level = self.current_header_level + 1

        if not self.body_name:
            if self.chaptersplit and self.current_header_level < self.chapterlevel:
                self.buff.append(HTMLFOOT)
                self.write_buff()

                self.buff = []
                self.current_file_index += 1
                self.current_file = 'index{0}.xhtml'.format(self.current_file_index)
                self.html_file_list.append(self.current_file)
                self.buff.append(HTMLHEAD)

                self.pages_list[self.current_file] = 0
                self.page_length = 0

        self.parse_format(elem, tag='div', css='section')

        if not self.body_name:
            level = 'h{0}'.format(self.current_header_level if self.current_header_level <= 6 else 6)
            vignette = self.get_vignette(level, 'chapterEnd')
            if vignette:
                self.buff.append('<p class="vignette_chapter_end"><img src="vignettes/{0}" /></p>'.format(vignette))
            self.buff.append('<div class="chapter_end"/>')

        self.current_header_level = max(0, self.current_header_level - 1)

    def parse_date(self, elem):
        self.parse_format(elem, 'time')

    def parse_format(self, elem, tag=None, css=None, href=None):
        dodropcaps = 0

        if elem.text:
            # Обработка dropcaps
            if self.first_chapter_line and not (self.header or self.subheader or self.body_name or self.no_paragraph):
                if tag == 'p':
                    if self.dropcaps == 'simple':
                        if elem.text[0] not in self.nodropcaps:
                            dodropcaps = 1
                            css = 'dropcaps'
                    elif self.dropcaps == 'smart':
                        for i, c in enumerate(elem.text):
                            if c not in self.nodropcaps and not c.isspace():
                                dodropcaps = i + 1
                                css = 'dropcaps'
                                break
                self.first_chapter_line = False

        if self.notes_mode in ('inline', 'block') and tag == 'a':
            note_id = href[1:]
            try:
                note = self.notes_dict[note_id]
                self.current_notes.append(note)
                tag = 'span'
                css = '{0}anchor'.format(self.notes_mode)
                href = None
            except KeyError:
                pass
        elif self.notes_mode in ('default', 'float') and tag == 'a':
            if href[1:] in self.notes_dict:
                elem.set('id', 'back_' + href[1:])
            else:
                css = 'linkanchor'

        if tag:
            self.buff.append('<{0}'.format(tag))
            if css:
                self.buff.append(' class="{0}"'.format(css))
            if 'id' in elem.attrib:
                new_id = save_html(sanitize_id(elem.attrib['id']))
                if new_id != elem.attrib['id']:
                    self.log.warning('id "{}" for tag "{}" was sanitized. This may create problems with links (TOC, notes) - it is better to fix original file.'.format(new_id, tag))
                self.buff.append(' id="{}"'.format(new_id))
                self.links_location[new_id] = self.current_file
            if href:
                self.buff.append(' href="{}"'.format(save_html(href)))
            if tag == 'p':
                # Для inline-картинок
                self.inline_image_mode = True
            if css == 'section':
                self.buff.append(' />')
            else:
                self.buff.append('>')

        if elem.text:
            if self.current_file in self.pages_list and self.page_length + len(elem.text) >= self.characters_per_page:
                page = self.pages_list[self.current_file]
                text = ''

                for w in elem.text.split(' '):
                    if not text:
                        text = ' ' if not w else w
                    else:
                        text = ' '.join([text, w])
                    if self.page_length + len(text) >= self.characters_per_page:
                        hs = self.insert_hyphenation(text)
                        if dodropcaps > 0:
                            self.buff.append('<span class="dropcaps">{}</span>{}'.format(save_html(hs[0:dodropcaps]), save_html(hs[dodropcaps:])))
                            dodropcaps = 0
                        else:
                            self.buff.append(save_html(hs))
                        self.buff.append('<a class="pagemarker" id="page_{0:d}"/> '.format(page))
                        page += 1
                        text = ''
                        self.page_length = 0

                self.page_length = len(text)
                if text:
                    hs = self.insert_hyphenation(text)
                    if dodropcaps > 0:
                        self.buff.append('<span class="dropcaps">{}</span>{}'.format(save_html(hs[0:dodropcaps]), save_html(hs[dodropcaps:])))
                    else:
                        self.buff.append(save_html(hs))
                self.pages_list[self.current_file] = page
            else:
                self.page_length += len(elem.text)
                hs = self.insert_hyphenation(elem.text)
                if dodropcaps > 0:
                    self.buff.append('<span class="dropcaps">{}</span>{}'.format(save_html(hs[0:dodropcaps]), save_html(hs[dodropcaps:])))
                else:
                    self.buff.append(save_html(hs))

        for e in elem:
            if e.tag == etree.Comment:
                continue
            if ns_tag(e.tag) == 'title':
                self.parse_title(e)
            elif ns_tag(e.tag) == 'subtitle':
                self.parse_subtitle(e)
            elif ns_tag(e.tag) == 'epigraph':
                self.parse_epigraph(e)
            elif ns_tag(e.tag) == 'annotation':
                self.parse_annotation(e)
            elif ns_tag(e.tag) == 'section':
                self.parse_section(e)
            elif ns_tag(e.tag) == 'strong':
                self.parse_strong(e)
            elif ns_tag(e.tag) == 'emphasis':
                self.parse_emphasis(e)
            elif ns_tag(e.tag) == 'strikethrough':
                self.parse_strikethrough(e)
            elif ns_tag(e.tag) == 'style':
                self.parse_style(e)
            elif ns_tag(e.tag) == 'a':
                self.parse_a(e)
            elif ns_tag(e.tag) == 'image':
                self.parse_image(e)
            elif ns_tag(e.tag) == 'p':
                self.parse_p(e)
            elif ns_tag(e.tag) == 'poem':
                self.parse_poem(e)
            elif ns_tag(e.tag) == 'stanza':
                self.parse_stanza(e)
            elif ns_tag(e.tag) == 'v':
                self.parse_v(e)
            elif ns_tag(e.tag) == 'cite':
                self.parse_cite(e)
            elif ns_tag(e.tag) == 'empty-line':
                self.parse_emptyline()
            elif ns_tag(e.tag) == 'text-author':
                self.parse_textauthor(e)
            elif ns_tag(e.tag) == 'table':
                self.parse_table(e)
            elif ns_tag(e.tag) == 'code':
                self.parse_code(e)
            elif ns_tag(e.tag) == 'date':
                self.parse_date(e)
            elif ns_tag(e.tag) == 'tr':
                self.parse_table_element(e)
            elif ns_tag(e.tag) == 'td':
                self.parse_table_element(e)
            elif ns_tag(e.tag) == 'th':
                self.parse_table_element(e)
            else:
                self.parse_other(e)

        if tag:
            if css == 'section':
                pass
            else:
                self.buff.append('</{0}>'.format(tag))
            # Для inline-картинок
            if tag == 'p':
                self.inline_image_mode = False

            if self.current_notes:
                if self.notes_mode == 'inline' and tag == 'span':
                    self.buff.append('<span class="inlinenote">{0}</span>'.format(save_html(self.insert_hyphenation(''.join(self.current_notes[0][1])))))
                    self.current_notes = []
                elif self.notes_mode == 'block' and tag == 'p':
                    self.buff.append('<div class="blocknote">')
                    for note in self.current_notes:
                        if note[1]:
                            self.buff.append('<p><span class="notenum">{0}) </span>{1}</p>'.format(save_html(note[0]), save_html(self.insert_hyphenation(''.join(note[1])))))
                    self.buff.append('</div>')
                    self.current_notes = []

        if elem.tail:
            self.page_length += len(elem.tail)
            self.buff.append(save_html(self.insert_hyphenation(elem.tail)))

    def parse_table_element(self, elem):
        self.buff.append('<{0}'.format(ns_tag(elem.tag)))

        for attr in elem.attrib:
            self.buff.append(' {0}="{1}"'.format(attr, elem.attrib[attr]))

        self.buff.append('>')
        self.parse_format(elem)
        self.buff.append('</{0}>'.format(ns_tag(elem.tag)))

    def insert_hyphenation(self, s):
        if not s:
            return ''
        return html.unescape(s) if not self.hyphenate or not self.hyphenator or self.header or self.subheader else self.hyphenator.hyphenate_text(html.unescape(s), self.replaceNBSP)

    def parse_body(self, elem):

        self.log.debug('Parsing body: {0}'.format(elem.attrib))

        self.body_name = elem.attrib['name'] if 'name' in elem.attrib else ''
        self.current_header_level = 0
        self.first_header_in_body = True

        if self.first_body:
            self.first_body = False
            self.body_name = ''

        self.buff = []
        self.buff.append(HTMLHEAD)

        if not self.body_name:
            self.current_file_index += 1
            self.current_file = 'index{0}.xhtml'.format(self.current_file_index)
            self.html_file_list.append(self.current_file)
        else:
            self.current_file = '{0}.xhtml'.format(hashlib.md5(bytes(self.body_name, 'utf-8')).hexdigest())
            self.html_file_list.append(self.current_file)

        self.pages_list[self.current_file] = 0
        self.page_length = 0

        if self.notes_mode in ('inline', 'block', 'float'):
            notes_bodies = self.notes_bodies.replace(' ', '').split(',')
            if self.body_name not in notes_bodies:
                self.parse_format(elem)
            elif self.notes_mode == 'float':

                # To satisfy Amazon's requirements for floating notes I have to create notes body on the fly here, removing most of the formatting

                if self.notes_order:
                    if self.body_name in self.notes_titles:
                        toc_title = self.notes_titles[self.body_name][0]
                        title = ''.join(self.notes_titles[self.body_name][1])
                    else:
                        toc_title = self.body_name[0].upper() + self.body_name[1:]
                        title = '<div class="h0"><p class="title">{0}</p></div>'.format(toc_title)

                    toc_ref_id = 'tocref{0}'.format(self.toc_index)
                    self.buff.append('<div class="titleblock" id="{0}">'.format(toc_ref_id))

                    vignette = self.get_vignette('h0', 'beforeTitle')
                    if vignette:
                        self.buff.append('<div class="vignette_title_before"><img src="vignettes/{0}" /></div>'.format(vignette))

                    self.buff.append(title)

                    vignette = self.get_vignette('h0', 'afterTitle')
                    if vignette:
                        self.buff.append('<div class="vignette_title_after"><img src="vignettes/{0}" /></div>'.format(vignette))

                    self.toc[self.toc_index] = ['{0}#{1}'.format(self.current_file, toc_ref_id), toc_title, 0, self.body_name]

                    self.buff.append('</div>')
                    self.toc_index += 1

                for noteid, body_name in self.notes_order:
                    if body_name == self.body_name:
                        note = self.notes_dict[noteid]
                        noteid_b = 'back_' + noteid
                        self.links_location[noteid] = self.current_file
                        # Sometimes due to an error document does not have a reference to note and numbers are all messed up
                        back_ref = 'nowhere'
                        try:
                            back_ref = self.links_location[noteid_b]
                        except:
                            pass
                        self.buff.append('<p class="floatnote"><a href="{0}#{1}" id="{2}">{3}).</a>&#160;{4}</p>'.format(back_ref, noteid_b, noteid, save_html(note[0]) if note[0] else '***', save_html(note[1])))
                    else:
                        continue
        else:
            self.parse_format(elem)

        self.buff.append(HTMLFOOT)
        self.write_buff()

    def generate_toc(self):
        self.buff = []
        self.buff.append(HTMLHEAD)
        self.current_file = 'toc.xhtml'

        self.buff.append('<div class="toc">')
        self.buff.append('<div class="h1" id="toc">{0}</div>'.format(self.toc_title))
        for (_, item) in self.toc.items():

            if item[2] <= self.toc_max_level:  # Ограничение уровня вложенности секций для TOC
                if item[3] == '':
                    ind = item[2] if item[2] <= 6 else 6
                    if ind == 0:
                        lines = item[1].splitlines()
                        self.buff.append('<div class="indent0"><a href="{0}">'.format(item[0]))
                        for line in lines:
                            if line.strip():
                                self.buff.append(save_html(line.strip()) + '<br/>')
                        self.buff.append('</a></div>')
                    else:
                        self.buff.append('<div class="indent{0}"><a href="{1}">{2}</a></div>'.format(ind, item[0], save_html(' '.join(item[1].split()))))
                else:
                    self.buff.append('<div class="indent0"><a href="{0}">{1}</a></div>'.format(item[0], save_html(' '.join(item[1].split()))))

        self.buff.append('</div>')
        self.buff.append(HTMLFOOT)

        self.write_buff()
        self.html_file_list.append(self.current_file)

    def ncx_navp_beg(self, index, title, link):
        self.buff.append('<navPoint id="navpoint{0}" playOrder="{1}">'.format(index, index))
        self.buff.append('<navLabel><text>{0}</text></navLabel>'.format(title))
        self.buff.append('<content src="{0}" />'.format(link))

    def ncx_navp_end(self):
        self.buff.append('</navPoint>')

    def generate_ncx(self):
        self.buff = []
        self.buff.append('<?xml version="1.0"?>' '<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="en-US">' '<head>')
        self.buff.append('<meta name="dtb:uid" content="urn:uuid:{0}"/>'.format(self.book_uuid))
        self.buff.append('</head>' '<docTitle>' '<text>fb2mobi.py</text>' '</docTitle>' '<navMap>')
        i = 1

        # Включим содержание в навигацию, если содержание помещается в начале книги
        if self.tocbeforebody and self.toc.items() and self.generate_toc_page:
            self.ncx_navp_beg(i, self.toc_title, 'toc.xhtml')
            self.ncx_navp_end()
            i += 1

        # First (book title) on the same level as the rest, if you want everything be under it do ncx_level = -1
        ncx_level = 2
        ncx_barrier = sys.maxsize

        if self.toc_type in 'flat':
            ncx_level = sys.maxsize
            ncx_barrier = 1

        if self.toc_type in 'kindle':
            ncx_level = self.toc_kindle_level
            ncx_barrier = 1

        history = []
        prev_item: Tuple[int, str] = ()
        for (_, item) in self.toc.items():
            if prev_item is ():  # first time
                self.ncx_navp_beg(i, save_html(' '.join(item[1].split())), item[0])
                history.append(item[2])
                i += 1
            elif prev_item[2] < item[2]:
                if item[2] < ncx_level or len(history) > ncx_barrier:
                    self.ncx_navp_end()
                    history.pop()
                self.ncx_navp_beg(i, save_html(' '.join(item[1].split())), item[0])
                history.append(item[2])
                i += 1
            elif prev_item[2] == item[2]:  # Same level
                self.ncx_navp_end()
                self.ncx_navp_beg(i, save_html(' '.join(item[1].split())), item[0])
            elif prev_item[2] > item[2]:  # Going out
                while history != [] and history[len(history) - 1] >= item[2]:
                    self.ncx_navp_end()
                    history.pop()
                self.ncx_navp_beg(i, save_html(' '.join(item[1].split())), item[0])
                history.append(item[2])
                i += 1
            else:
                assert False
            prev_item = item

        # Whatever levels are open - close them
        while history != []:
            self.ncx_navp_end()
            history.pop()

        # Включим содержание в навигацию, если содержание помещается в конце книги
        if not self.tocbeforebody and self.toc.items() and self.generate_toc_page:
            self.ncx_navp_beg(i, self.toc_title, 'toc.xhtml')
            self.ncx_navp_end()

        self.buff.append('</navMap></ncx>')
        self.write_buff(self.temp_content_dir, 'toc.ncx')

    def generate_mimetype(self):
        mimetype = 'application/epub+zip'
        write_file(mimetype, os.path.join(self.temp_dir, 'mimetype'))

    def generate_container(self):
        self.buff = []
        self.buff.append('<?xml version="1.0"?>'
                         '<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">'
                         '<rootfiles>'
                         '<rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>'
                         '</rootfiles>'
                         '</container>')
        self.write_buff(self.temp_inf_dir, 'container.xml')

    def stamp_cover(self, img):


        # Most of numbers/sizes are totally arbitrary...

        if self.cover_stamp == 'None':
            return

        if not self.cover_font or not os.path.isfile(self.cover_font):
            self.log.warn('Unable to place stamp on cover - coverFont is not specified')
            return

        title = '' if not self.book_title else self.book_title.strip()
        series = '' if not self.book_series else self.book_series.strip()
        if self.book_series_num:
            series = '{0}: {1}'.format(series, self.book_series_num.strip())
        author = self.get_book_authors()
        if len(author) > 25:
            author = author[0:25] + u"\u2026"

        # tuning
        h = img.height // 4
        fh = max(10, h // 6)
        off = fh // 4

        if self.cover_stamp == 'Top':
            pos = (0, 0)
        elif self.cover_stamp == 'Bottom':
            pos = (0, img.height - h)
        else:
            pos = (0, (img.height - h) // 2)

        overlay = Image.new('RGBA', (img.width, h), color=(0, 0, 0, 200))

        step = off
        if title:
            _, h = ImageText(overlay).write_text_box((off, step), title, box_width=img.width - off, font_filename=self.cover_font, font_size=fh, color=(255, 255, 255))
            step += h
        if series:
            _, h = ImageText(overlay).write_text_box((off, step + off), series, box_width=img.width - off, font_filename=self.cover_font, font_size=fh, color=(255, 255, 255))
            step += h
        if author:
            ImageText(overlay).write_text_box((off, step + off), author, box_width=img.width - off, font_filename=self.cover_font, font_size=fh, color=(255, 255, 255))

        img.paste(overlay, pos)

    def generate_cover(self):
        filename = None
        if self.book_cover:
            for imgid, _, file in self.image_file_list:
                if imgid == self.book_cover:
                    filename = file
                    break
            if not filename:
                self.log.error('Unable to find book cover image for ref-id "{0}". Disabling book cover...'.format(self.book_cover))
                self.book_cover = ''
                return

            # make sure kindlegen does not complain on cover size and make sure that epub cover takes whole screen
            full_name = os.path.join(self.temp_content_dir, 'images', filename)
            im = Image.open(full_name)
            if im.height < self.screen_height:
                im = im.resize((int(self.screen_height * im.width / im.height), self.screen_height), Image.LANCZOS)

            self.stamp_cover(im)
            # im.save(full_name, optimize=True, quality=50)
            im.save(full_name)

            if not self.kindle:
                self.buff = []
                self.buff.append(HTMLHEAD)
                self.buff.append('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 {0} {1}" preserveAspectRatio="xMidYMid meet">'.format(
                    self.screen_width, self.screen_height))
                self.buff.append('<image width="{0}" height="{1}" xlink:href="images/{2}" />'.format(self.screen_width, self.screen_height, filename))
                self.buff.append('</svg>')
                self.buff.append(HTMLFOOT)
                self.current_file = 'cover.xhtml'

                self.write_buff()

    def generate_pagemap(self):
        page = 1
        self.buff = []
        self.buff.append('<?xml version = "1.0" ?>' '<page-map xmlns = "http://www.idpf.org/2007/opf">')

        if self.book_cover and not self.kindle:
            self.buff.append('<page name="{0}" href="cover.xhtml"/>'.format(page))
            page += 1
        if self.tocbeforebody and self.generate_toc_page:
            self.buff.append('<page name="{0}" href="toc.xhtml"/>'.format(page))
            page += 1

        for item in self.html_file_list:
            if item != 'toc.xhtml':
                self.buff.append('<page name="{0}" href="{1}"/>'.format(page, item))
                page += 1
                if item in self.pages_list:
                    for p in range(0, self.pages_list[item]):
                        self.buff.append('<page name="{0:d}" href="{1:s}#page_{2:d}"/>'.format(page, item, p))
                        page += 1

        if not self.tocbeforebody and self.generate_toc_page:
            self.buff.append('<page name="{0}" href="toc.xhtml"/>'.format(page))
            page += 1

        self.buff.append('</page-map>')
        self.write_buff(self.temp_content_dir, 'page-map.xml')

    def generate_opf(self):
        self.buff = []
        self.buff.append('<?xml version="1.0" ?>' '<package version="2.0" xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookId">' '<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">')

        title = self.book_title

        if self.booktitleformat:
            # pylint: disable=C0330
            # yapf: disable
            title = format_pattern(self.booktitleformat,
                [
                    ('#title', '' if not self.book_title else self.book_title.strip()),
                    ('#series', '' if not self.book_series else self.book_series.strip()),
                    ('#abbrseries', ''.join(word[0] for word in self.book_series.split()).lower() if self.book_series else ''),
                    ('#number', '' if not self.book_series_num else self.book_series_num.strip()),
                    ('#padnumber', '' if not self.book_series_num else self.book_series_num.strip().zfill(self.seriespositions)),
                    ('#date', '' if not self.book_date else self.book_date.strip())
                ])
            # yapf: enable
            if not title:
                title = self.book_title

        title = title.strip()
        if self.transliterate_author_and_title:
            title = slugify(title, separator=' ')

        self.buff.append('<dc:title>{0}</dc:title>'.format(save_html(title)))
        self.buff.append('<dc:language>{0}</dc:language>'.format(self.book_lang))
        self.buff.append('<dc:identifier id="BookId" opf:scheme="uuid">urn:uuid:{0}</dc:identifier>'.format(self.book_uuid))
        for a in self.book_authors:
            if self.transliterate_author_and_title:
                a = slugify(a, ' ')
            self.buff.append('<dc:creator opf:role="aut">{0}</dc:creator>'.format(save_html(a)))
        self.buff.append('<dc:publisher />')

        for genre in self.genres:
            self.buff.append('<dc:subject>{0}</dc:subject>'.format(genre))

        if self.annotation:
            self.buff.append('<dc:description>{0}</dc:description>'.format(save_html(self.annotation)))

        if self.book_cover:
            self.buff.append('<meta name="cover" content="cover-image" />')

        self.buff.append('</metadata>')
        self.buff.append('<manifest>' '<item id="ncx" media-type="application/x-dtbncx+xml" href="toc.ncx"/>' '<item id = "map" media-type="application/oebps-page-map+xml" href="page-map.xml"/>')

        for item in self.html_file_list:
            self.buff.append('<item id="{0}" media-type="application/xhtml+xml" href="{1}"/>'.format(item.split('.')[0], item))

        item_id = 0
        for imgid, imgtype, filename in self.image_file_list:
            if imgid == self.book_cover:
                self.buff.append('<item id="cover-image" media-type="{0}" href="images/{1}"/>'.format(imgtype, filename))
                if not self.kindle:
                    self.buff.append('<item id="cover-page" href="cover.xhtml" media-type="application/xhtml+xml"/>')
            else:
                self.buff.append('<item id="image{0}" media-type="{1}" href="images/{2}"/>'.format(item_id, imgtype, filename))

            item_id += 1

        for item in self.vignette_files:
            item_file = os.path.split(item)[1]
            item_type = os.path.splitext(item_file)[1]
            item_type = item_type[1:]

            if item_type == 'jpg':
                item_type = 'jpeg'

            self.buff.append('<item id="image{0}" media-type="image/{1}" href="vignettes/{2}"/>'.format(item_id, item_type, item_file))
            item_id += 1

        self.buff.append('<item id="style" href="stylesheet.css" media-type="text/css"/>')

        font_id = 0
        for f in self.font_list:
            if f.lower().endswith('.otf'):
                self.buff.append('<item id="font{0}" href="{1}" media-type="application/opentype"/>'.format(font_id, f))
            else:
                self.buff.append('<item id="font{0}" href="{1}" media-type="application/x-font-ttf"/>'.format(font_id, f))
            font_id += 1

        self.buff.append('</manifest>' '<spine page-map="map" toc="ncx">')

        if self.book_cover and not self.kindle:
            self.buff.append('<itemref idref="cover-page" linear="no"/>')
        if self.tocbeforebody and self.generate_toc_page:
            self.buff.append('<itemref idref="toc"/>')

        for item in self.html_file_list:
            if item != 'toc.xhtml':
                self.buff.append('<itemref idref="{0}"/>'.format(item.split('.')[0]))

        if not self.tocbeforebody and self.generate_toc_page:
            self.buff.append('<itemref idref="toc"/>')

        self.buff.append('</spine>')

        if self.generate_opf_guide:
            self.buff.append('<guide>')
            if self.book_cover and not self.kindle:
                self.buff.append('<reference type="cover-page" href="cover.xhtml"/>')

            if self.open_book_from_cover and self.book_cover:
                self.buff.append('<reference type="text" title="book" href="cover.xhtml"/>')
            else:
                for item in self.html_file_list:
                    if item.split('.')[0].startswith('index'):
                        self.buff.append('<reference type="text" title="Starts here" href="{0}"/>'.format(item))
                        break

            self.buff.append('<reference type="toc" title="Table of Contents" href="toc.xhtml"/>')
            self.buff.append('</guide>')

        self.buff.append('</package>')

        self.write_buff(self.temp_content_dir, 'content.opf')

    def get_buff(self):
        return ''.join(self.buff) class EpubProc:
    def __init__(self, opffile, config):
        self.buff = []

        self.book_title = ''  # Название книги
        self.book_author = ''  # Автор
        self.book_series = ''  # Книжная серия
        self.book_series_num = ''  # Номер в книжной серии

        self.book_lang = 'ru'

        self.hyphenate = config.current_profile['hyphens']
        if self.hyphenate:
            self.replaceNBSP = config.current_profile['hyphensReplaceNBSP']
            self.hyphenator = MyHyphen(self.book_lang)

        self.opffile = opffile
        self.path = os.path.dirname(opffile)

        self.log = config.log
        self.bookseriestitle = config.current_profile['bookTitleFormat']
        self.transliterate_author_and_title = config.transliterate_author_and_title

        self.book_uuid = uuid.uuid4()

        self.tree = etree.parse(opffile, parser=etree.XMLParser(recover=True))
        self.root = self.tree.getroot()

    def insert_hyphenation(self, s):
        if not s:
            return ''
        return html.unescape(s) if not self.hyphenator or not self.hyphenate else self.hyphenator.hyphenate_text(html.unescape(s), self.replaceNBSP)

    def process(self):

        # get uuid if any
        for node in self.root.iter('{*}identifier'):
            try:
                self.book_uuid = uuid.UUID(node.text)
            except:
                pass
            break
        # Lookup series/sequences data if any
        for node in self.root.iter('{*}meta'):
            attributes = node.attrib
            if 'name' in attributes:
                if attributes['name'].endswith('series_index'):
                    self.book_series_num = attributes['content']
                elif attributes['name'].endswith('series'):
                    self.book_series = attributes['content']

        # And reformat book title accordingly
        if self.book_series != '':
            for node in self.root.iter('{*}title'):
                self.book_title = node.text
                abbr = ''.join(word[0] for word in self.book_series.split())
                title = self.bookseriestitle
                title = title.replace('#series', '' if not self.book_series else self.book_series.strip())
                title = title.replace('#number', '' if not self.book_series_num else self.book_series_num.strip())
                title = title.replace('#padnumber', '' if not self.book_series_num else self.book_series_num.strip().zfill(2))
                title = title.replace('#title', '' if not self.book_title else self.book_title.strip())
                title = title.replace('#abbrseries', '' if not abbr else abbr.lower())
                if self.transliterate_author_and_title:
                    title = slugify(title, separator=' ')
                node.text = title

        for node in self.root.iter('{*}language'):
            self.book_lang = node.text if len(node.text) > 2 else node.text.lower()

        if self.book_lang in ('rus'):
            self.book_lang = 'ru'

        if self.hyphenate and self.hyphenator:
            try:
                self.hyphenator.set_language(self.book_lang.replace("-", "_"))
            except:
                self.log.warning('Unable to set hyphenation dictionary for language code "{}" - turning hyphenation off'.format(self.book_lang))
                self.hyphenate = False

        self.tree.write(self.opffile, encoding='utf-8', method='xml', xml_declaration=True, pretty_print=True)

        # See if we have items to correct and process
        for node in self.root.iter('{*}item'):
            attributes = node.attrib
            if 'href' in attributes and 'media-type' in attributes:
                if attributes['media-type'] == 'application/xhtml+xml':
                    filename = os.path.join(self.path, attributes['href'])
                    self.log.debug('Processing {}'.format(filename))
                    # Proper XML encoding needed by kndlegen
                    xhtml = etree.parse(filename, parser=etree.XMLParser(recover=True))
                    if self.hyphenate:
                        # Do hyphenation if desiried
                        for body in xhtml.getroot().iter('{*}body'):
                            for elem in body.iter('{*}p', '{*}div'):
                                if elem.text:
                                    elem.text = save_html(self.insert_hyphenation(elem.text))
                                if elem.tail:
                                    elem.tail = save_html(self.insert_hyphenation(elem.tail))
                                for child in elem.iterchildren(tag=etree.Element):
                                    if not child.tag.endswith('pre'):
                                        if child.text:
                                            child.text = save_html(self.insert_hyphenation(child.text))
                                        if child.tail:
                                            child.tail = save_html(self.insert_hyphenation(child.tail))
                    xhtml.write(filename, encoding='utf-8', method='xml', xml_declaration=True)

                    # TODO: Do we need profile per file extension?
                    # TODO: Replace stylesheets?
                    # TODO: book_author = slugify(book_author, separator=' ') class ConverterConfig:

    def __init__(self, config_file):
        self.config_file = config_file

        self.debug = False
        self.log_file = None
        # self.log_file = 'aaaa.log'
        self.original_log_file = None
        # self.log_level = 'Debug'
        self.log_level = 'Info'
        self.console_level = 'Info'
        self.output_format = 'epub'
        self.kindle_compression_level = 1
        self.no_dropcaps_symbols = '\'"-.…0123456789‒–—«»'
        self.transliterate = False
        self.transliterate_author_and_title = False
        self.noMOBIoptimization = False
        self.screen_height = 800
        self.screen_width = 573
        self.default_profile = 'default'

        self.input_dir = None
        self.output_dir = None
        self.output_pattern = None
        self.save_structure = None
        self.delete_source_file = None

        self.apnx = None
        self.characters_per_page = 2300

        self.log = None

        self.profiles = {}
        self.profiles['default'] = {}
        self.profiles['default']['name'] = 'default'
        self.profiles['default']['description'] = 'Default profile'
        self.profiles['default']['outputFormat'] = None
        self.profiles['default']['transliterate'] = None
        self.profiles['default']['screenHeight'] = None
        self.profiles['default']['screenWidth'] = None
        self.profiles['default']['transliterateAuthorAndTitle'] = None
        self.profiles['default']['hyphens'] = True
        self.profiles['default']['hyphensReplaceNBSP'] = True
        self.profiles['default']['dropcaps'] = 'None'
        self.profiles['default']['tocMaxLevel'] = 1000
        self.profiles['default']['tocKindleLevel'] = 2
        self.profiles['default']['tocBeforeBody'] = False
        self.profiles['default']['tocType'] = 'Normal'
        self.profiles['default']['originalcss'] = 'default.css'
        self.profiles['default']['css'] = 'default.css'
        self.profiles['default']['parse_css'] = True
        self.profiles['default']['chapterOnNewPage'] = True
        self.profiles['default']['chapterLevel'] = 100
        self.profiles['default']['authorFormat'] = '#l #f #m'
        self.profiles['default']['bookTitleFormat'] = '(#abbrseries #number) #title'
        self.profiles['default']['annotationTitle'] = 'Annotation'
        self.profiles['default']['tocTitle'] = 'Content'
        self.profiles['default']['notesMode'] = 'default'
        self.profiles['default']['notesBodies'] = 'notes'
        self.profiles['default']['vignettes'] = {}
        self.profiles['default']['vignettes_save'] = {}
        self.profiles['default']['generateTOCPage'] = True
        self.profiles['default']['generateAnnotationPage'] = True
        self.profiles['default']['generateOPFGuide'] = True
        self.profiles['default']['kindleRemovePersonalLabel'] = True
        self.profiles['default']['removePngTransparency'] = False
        self.profiles['default']['generateAPNX'] = None
        self.profiles['default']['charactersPerPage'] = 2300
        self.profiles['default']['seriesPositions'] = 2
        self.profiles['default']['openBookFromCover'] = False
        self.profiles['default']['scaleImages'] = 0.0
        self.profiles['default']['coverDefault'] = 'default_cover.jpg'
        self.profiles['default']['coverStamp'] = 'None'
        self.profiles['default']['outputPattern'] = None

        self.current_profile = {}
        self.mhl = False
        self.recursive = False

        self.send_to_kindle = {}
        self.send_to_kindle['send'] = False
        self.send_to_kindle['deleteSendedBook'] = True
        self.send_to_kindle['smtpServer'] = 'smtp.gmail.com'
        self.send_to_kindle['smtpPort'] = 465
        self.send_to_kindle['smtpLogin'] = '[Your Google Email]'
        self.send_to_kindle['smtpPassword'] = '[Your Google Password]'
        self.send_to_kindle['fromUserEmail'] = '[Your Google Email]'
        self.send_to_kindle['toKindleEmail'] = '[Your Kindle Email]'

        # if no configuration - provide minimum defaults
        if not os.path.exists(self.config_file):

            # configuration
            self.write()

            # default css
            default_css = import_module('modules.default_css')
            default_css_path = os.path.abspath(os.path.join(os.path.dirname(self.config_file), 'default.css'))
            make_dir(default_css_path)
            with codecs.open(default_css_path, "w", 'utf-8') as f:
                f.write(default_css.default_css)
                f.close()

            # default cover
            default_cover_path = os.path.join(get_executable_path(), 'default_cover.jpg')
            if os.path.exists(default_cover_path):
                copy_file(default_cover_path, os.path.abspath(os.path.join(os.path.dirname(self.config_file), 'default_cover.jpg')))

        self._load()

    def _load(self):
        config = etree.parse(self.config_file)
        for e in config.getroot():
            if e.tag == 'debug':
                self.debug = e.text.lower() == 'true'

            elif e.tag == 'logFile':
                if e.text:
                    self.original_log_file = e.text
                    self.log_file = os.path.abspath(os.path.join(os.path.dirname(self.config_file), e.text))

            elif e.tag == 'logLevel':
                self.log_level = e.text

            elif e.tag == 'consoleLevel':
                self.console_level = e.text

            elif e.tag == 'outputFormat':
                self.output_format = e.text

            elif e.tag == 'outputPattern':
                self.output_pattern = e.text

            elif e.tag == 'kindleCompressionLevel':
                self.kindle_compression_level = int(e.text)

            elif e.tag == 'noDropcapsSymbols':
                self.no_dropcaps_symbols = e.text

            elif e.tag == 'transliterate':
                self.transliterate = e.text.lower() == 'true'

            elif e.tag == 'screenWidth':
                self.screen_width = int(e.text)

            elif e.tag == 'screenHeight':
                self.screen_height = int(e.text)

            elif e.tag == 'defaultProfile':
                self.default_profile = e.text

            elif e.tag == 'noMOBIoptimization':
                self.noMOBIoptimization = e.text.lower() == 'true'

            elif e.tag == 'sendToKindle':
                for s in e:
                    if s.tag == 'send':
                        self.send_to_kindle['send'] = s.text.lower() == 'true'

                    elif s.tag == 'deleteSendedBook':
                        self.send_to_kindle['deleteSendedBook'] = s.text.lower() == 'true'

                    elif s.tag == 'smtpServer':
                        self.send_to_kindle['smtpServer'] = s.text

                    elif s.tag == 'smtpPort':
                        self.send_to_kindle['smtpPort'] = int(s.text)

                    elif s.tag == 'smtpLogin':
                        self.send_to_kindle['smtpLogin'] = s.text

                    elif s.tag == 'smtpPassword':
                        self.send_to_kindle['smtpPassword'] = s.text

                    elif s.tag == 'fromUserEmail':
                        self.send_to_kindle['fromUserEmail'] = s.text

                    elif s.tag == 'toKindleEmail':
                        self.send_to_kindle['toKindleEmail'] = s.text

            elif e.tag == 'profiles':
                self.profiles = {}
                for prof in e:
                    prof_name = prof.attrib['name']
                    self.profiles[prof_name] = {}
                    self.profiles[prof_name]['name'] = prof.attrib['name']
                    self.profiles[prof_name]['description'] = prof.attrib['description']
                    self.profiles[prof_name]['vignettes'] = {}
                    self.profiles[prof_name]['generateTOCPage'] = True
                    self.profiles[prof_name]['generateAnnotationPage'] = True
                    self.profiles[prof_name]['generateOPFGuide'] = True
                    self.profiles[prof_name]['tocType'] = 'Normal'
                    self.profiles[prof_name]['kindleRemovePersonalLabel'] = True
                    self.profiles[prof_name]['removePngTransparency'] = False
                    self.profiles[prof_name]['generateAPNX'] = None
                    self.profiles[prof_name]['charactersPerPage'] = 2300
                    self.profiles[prof_name]['tocMaxLevel'] = 1000
                    self.profiles[prof_name]['tocKindleLevel'] = 2
                    self.profiles[prof_name]['seriesPositions'] = 2
                    self.profiles[prof_name]['chapterLevel'] = 100
                    self.profiles[prof_name]['openBookFromCover'] = False
                    self.profiles[prof_name]['coverStamp'] = 'None'
                    self.profiles[prof_name]['coverTextFont'] = None
                    self.profiles[prof_name]['scaleImages'] = 0.0
                    self.profiles[prof_name]['coverDefault'] = os.path.abspath(os.path.join(os.path.dirname(self.config_file), 'default_cover.jpg'))

                    for p in prof:
                        if p.tag == 'hyphens':
                            self.profiles[prof_name]['hyphens'] = p.text.lower() == 'true'

                        elif p.tag == 'hyphensReplaceNBSP':
                            self.profiles[prof_name]['hyphensReplaceNBSP'] = p.text.lower() == 'true'

                        elif p.tag == 'dropcaps':
                            self.profiles[prof_name]['dropcaps'] = p.text

                        elif p.tag == 'outputFormat':
                            self.profiles[prof_name]['outputFormat'] = p.text

                        elif p.tag == 'outputPattern':
                            self.profiles[prof_name]['outputPattern'] = p.text

                        elif p.tag == 'transliterate':
                            self.profiles[prof_name]['transliterate'] = p.text.lower() == 'true'

                        elif p.tag == 'screenWidth':
                            self.profiles[prof_name]['screenWidth'] = int(p.text)

                        elif p.tag == 'screenHeight':
                            self.profiles[prof_name]['screenHeight'] = int(p.text)

                        elif p.tag == 'transliterateAuthorAndTitle':
                            self.profiles[prof_name]['transliterateAuthorAndTitle'] = p.text.lower() == 'true'

                        elif p.tag == 'tocMaxLevel':
                            self.profiles[prof_name]['tocMaxLevel'] = int(p.text)

                        elif p.tag == 'tocKindleLevel':
                            self.profiles[prof_name]['tocKindleLevel'] = int(p.text)

                        elif p.tag == 'generateTOCPage':
                            self.profiles[prof_name]['generateTOCPage'] = p.text.lower() == 'true'

                        elif p.tag == 'flatTOC':
                            print("***WARNING: <flatTOC> is ignored, use <tocType> instead!")

                        elif p.tag == 'tocType':
                            if p.text.lower() in ['flat', 'kindle', 'normal']:
                                self.profiles[prof_name]['tocType'] = p.text.lower()

                        elif p.tag == 'kindleRemovePersonalLabel':
                            self.profiles[prof_name]['kindleRemovePersonalLabel'] = p.text.lower() == 'true'

                        elif p.tag == 'removePngTransparency':
                            self.profiles[prof_name]['removePngTransparency'] = p.text.lower() == 'true'

                        elif p.tag == 'generateAPNX':
                            if p.text.lower() in ['eink', 'pc']:
                                self.profiles[prof_name]['generateAPNX'] = p.text.lower()

                        elif p.tag == 'charactersPerPage':
                            self.profiles[prof_name]['charactersPerPage'] = int(p.text)

                        elif p.tag == 'seriesPositions':
                            self.profiles[prof_name]['seriesPositions'] = int(p.text)

                        elif p.tag == 'generateAnnotationPage':
                            self.profiles[prof_name]['generateAnnotationPage'] = p.text.lower() == 'true'

                        elif p.tag == 'generateOPFGuide':
                            self.profiles[prof_name]['generateOPFGuide'] = p.text.lower() == 'true'

                        elif p.tag == 'tocBeforeBody':
                            self.profiles[prof_name]['tocBeforeBody'] = p.text.lower() == 'true'

                        elif p.tag == 'css':
                            self.profiles[prof_name]['originalcss'] = p.text
                            self.profiles[prof_name]['css'] = os.path.abspath(os.path.join(os.path.dirname(self.config_file), p.text))
                            if 'parse' in p.attrib:
                                self.profiles[prof_name]['parse_css'] = p.attrib['parse'].lower() == 'true'
                            else:
                                self.profiles[prof_name]['parse_css'] = True

                        elif p.tag == 'xslt':
                            self.profiles[prof_name]['xslt'] = os.path.abspath(os.path.join(os.path.dirname(self.config_file), p.text))

                        elif p.tag == 'chapterOnNewPage':
                            self.profiles[prof_name]['chapterOnNewPage'] = p.text.lower() == 'true'

                        elif p.tag == 'chapterLevel':
                            self.profiles[prof_name]['chapterLevel'] = int(p.text)

                        elif p.tag == 'authorFormat':
                            self.profiles[prof_name]['authorFormat'] = p.text

                        elif p.tag == 'bookTitleFormat':
                            self.profiles[prof_name]['bookTitleFormat'] = p.text

                        elif p.tag == 'annotationTitle':
                            self.profiles[prof_name]['annotationTitle'] = p.text

                        elif p.tag == 'tocTitle':
                            self.profiles[prof_name]['tocTitle'] = p.text

                        elif p.tag == 'notesMode':
                            self.profiles[prof_name]['notesMode'] = p.text

                        elif p.tag == 'notesBodies':
                            self.profiles[prof_name]['notesBodies'] = p.text

                        elif p.tag == 'vignettes':
                            self.profiles[prof_name]['vignettes'] = {}
                            self.profiles[prof_name]['vignettes_save'] = {}

                            for vignettes in p:
                                vignettes_level = vignettes.attrib['level']
                                vign_arr = {}
                                vign_arr_save = {}

                                for v in vignettes:
                                    vign_arr[v.tag] = None if v.text.lower() == 'none' else os.path.abspath(os.path.join(os.path.dirname(self.config_file), v.text))
                                    vign_arr_save[v.tag] = None if v.text.lower() == 'none' else v.text

                                self.profiles[prof_name]['vignettes'][vignettes_level] = vign_arr
                                self.profiles[prof_name]['vignettes_save'][vignettes_level] = vign_arr_save

                        elif p.tag == 'openBookFromCover':
                            self.profiles[prof_name]['openBookFromCover'] = p.text.lower() == 'true'

                        elif p.tag == 'coverStamp':
                            self.profiles[prof_name]['coverStamp'] = p.text

                        elif p.tag == 'coverDefault':
                            self.profiles[prof_name]['coverDefault'] = os.path.abspath(os.path.join(os.path.dirname(self.config_file), p.text))

                        elif p.tag == 'coverTextFont':
                            self.profiles[prof_name]['coverTextFont'] = os.path.abspath(os.path.join(os.path.dirname(self.config_file), p.text))

                        elif p.tag == 'scaleImages':
                            self.profiles[prof_name]['scaleImages'] = float(p.text)

    def _getVignette(self, vignette_arr):
        for v in vignette_arr:
            print(v)

    def _getVignettes(self, profile):
        result = []

        try:
            for v in self.profiles[profile]['vignettes_save']:
                result.append(
                    E('vignette',
                      E('beforeTitle', self.profiles[profile]['vignettes_save'][v]['beforeTitle'] if self.profiles[profile]['vignettes_save'][v]['beforeTitle'] else 'None'),
                      E('afterTitle', self.profiles[profile]['vignettes_save'][v]['afterTitle'] if self.profiles[profile]['vignettes_save'][v]['afterTitle'] else 'None'),
                      E('chapterEnd', self.profiles[profile]['vignettes_save'][v]['chapterEnd'] if self.profiles[profile]['vignettes_save'][v]['chapterEnd'] else 'None'),
                      level=v))

        except:
            pass

        return result

    def _getProfiles(self):
        result = []
        for p in self.profiles:
            result.append(
                E('profile',
                  E('hyphens', str(self.profiles[p]['hyphens'])),
                  E('hyphensReplaceNBSP', str(self.profiles[p]['hyphensReplaceNBSP'])),
                  E('dropcaps', str(self.profiles[p]['dropcaps'])),
                  E('tocMaxLevel', str(self.profiles[p]['tocMaxLevel'])),
                  E('tocKindleLevel', str(self.profiles[p]['tocKindleLevel'])),
                  E('tocBeforeBody', str(self.profiles[p]['tocBeforeBody'])),
                  E('tocType', str(self.profiles[p]['tocType'])),
                  E('css', self.profiles[p]['originalcss'], parse=str(self.profiles[p]['parse_css'])),
                  E('chapterOnNewPage', str(self.profiles[p]['chapterOnNewPage'])),
                  E('chapterLevel', str(self.profiles[p]['chapterLevel'])),
                  E('authorFormat', self.profiles[p]['authorFormat']),
                  E('bookTitleFormat', self.profiles[p]['bookTitleFormat']),
                  E('annotationTitle', self.profiles[p]['annotationTitle']),
                  E('tocTitle', self.profiles[p]['tocTitle']),
                  E('notesMode', self.profiles[p]['notesMode']),
                  E('notesBodies', self.profiles[p]['notesBodies']),
                  E('generateTOCPage', str(self.profiles[p]['generateTOCPage'])),
                  E('generateAnnotationPage', str(self.profiles[p]['generateAnnotationPage'])),
                  E('generateOPFGuide', str(self.profiles[p]['generateOPFGuide'])),
                  E('kindleRemovePersonalLabel', str(self.profiles[p]['kindleRemovePersonalLabel'])),
                  E('removePngTransparency', str(self.profiles[p]['removePngTransparency'])),
                  E('charactersPerPage', str(self.profiles[p]['charactersPerPage'])),
                  E('seriesPositions', str(self.profiles[p]['seriesPositions'])),
                  E('openBookFromCover', str(self.profiles[p]['openBookFromCover'])),
                  E('coverDefault', str(self.profiles[p]['coverDefault'])),
                  E('coverStamp', str(self.profiles[p]['coverStamp'])),
                  E('vignettes', *self._getVignettes(p)),
                  name=p,
                  description=self.profiles[p]['description']))

        return result

    def setCurrentProfile(self, profile_name):
        try:
            self.current_profile = self.profiles[profile_name]
        except:
            print('***WARNING: Unable to locate profile "{0}". Using default one.'.format(profile_name))
            self.current_profile = self.profiles[self.default_profile]

        if 'outputFormat' in self.current_profile:
            self.output_format = self.current_profile['outputFormat']

        if 'outputPattern' in self.current_profile:
            self.output_pattern = self.current_profile['outputPattern']

        if 'transliterate' in self.current_profile:
            self.transliterate = self.current_profile['transliterate']

        if 'screenWidth' in self.current_profile:
            self.screen_width = self.current_profile['screenWidth']

        if 'screenHeight' in self.current_profile:
            self.screen_height = self.current_profile['screenHeight']

        if 'transliterateAuthorAndTitle' in self.current_profile:
            self.transliterate_author_and_title = self.current_profile['transliterateAuthorAndTitle']

        if 'generateAPNX' in self.current_profile:
            self.apnx = self.current_profile['generateAPNX']

        if 'charactersPerPage' in self.current_profile:
            self.characters_per_page = self.current_profile['charactersPerPage']

    def write(self):
        # yapf: disable
        config = E('settings',
            # pylint: disable=C0330
            E('debug', str(self.debug)),
            E('logFile', self.original_log_file) if self.original_log_file else E('logFile'),
            E('logLevel', self.log_level),
            E('consoleLevel', self.console_level),
            E('outputFormat', self.output_format),
            E('kindleCompressionLevel', str(self.kindle_compression_level)),
            E('noDropcapsSymbols', self.no_dropcaps_symbols),
            E('transliterate', str(self.transliterate)),
            E('screenWidth', str(self.screen_width)),
            E('screenHeight', str(self.screen_height)),
            E('defaultProfile', self.default_profile),
            E('noMOBIoptimization', str(self.noMOBIoptimization)),
            E('profiles', *self._getProfiles()),
            E('sendToKindle',
                E('send', str(self.send_to_kindle['send'])),
                E('deleteSendedBook', str(self.send_to_kindle['deleteSendedBook'])),
                E('smtpServer', self.send_to_kindle['smtpServer']),
                E('smtpPort', str(self.send_to_kindle['smtpPort'])),
                E('smtpLogin', self.send_to_kindle['smtpLogin']),
                E('smtpPassword', self.send_to_kindle['smtpPassword']) if self.send_to_kindle['smtpPassword'] else E('smtpPassword'),
                E('fromUserEmail', self.send_to_kindle['fromUserEmail']),
                E('toKindleEmail', self.send_to_kindle['toKindleEmail'])))
        # yapf: enable

        config_dir = os.path.dirname(self.config_file)
        if not os.path.exists(config_dir):
            os.makedirs(config_dir)

        with codecs.open(self.config_file, "w", 'utf-8') as f:
            f.write(str(etree.tostring(config, encoding="utf-8", pretty_print=True, xml_declaration=True), 'utf-8'))
            f.close() class SendToKindle:
    def __init__(self):
        self.smtp_server = None
        self.smtp_port = 0
        self.smtp_login = None
        self.user_email = None
        self.kindle_email = None
        self.smtp_password = None
        self.convert = False

        self.parser = None

    def send_mail(self, files):
        msg = MIMEMultipart()
        msg['From'] = self.user_email
        msg['To'] = self.kindle_email
        msg['Subject'] = 'Convert' if self.convert else 'Sent to Kindle'
        msg.preamble = 'This email has been automatically sent by fb2mobi tool'

        for file_path in files:
            fname = os.path.basename(file_path)
            msg.attach(MIMEApplication(open(file_path, 'rb').read(), Content_Disposition='attachment; filename="%s"' % fname, Name=fname))

        mail_server = smtplib.SMTP(host=self.smtp_server, port=self.smtp_port)
        mail_server.starttls()
        mail_server.login(self.smtp_login, self.smtp_password)
        mail_server.sendmail(self.user_email, self.kindle_email, msg.as_string())
        mail_server.quit() class mobi_split:
    def __init__(self, infile, document_id, remove_personal_label, format):
        if format == 'mobi':
            datain = b''
            with open(pathof(infile), 'rb') as f:
                datain = f.read()
            datain_rec0 = readsection(datain, 0)
            ver = getint(datain_rec0, mobi_version)
            self.combo = (ver != 8)
            if not self.combo:
                return
            exth121 = read_exth(datain_rec0, 121)
            if len(exth121) == 0:
                self.combo = False
                return
            else:
                # only pay attention to first exth121
                # (there should only be one)
                datain_kf8, = struct.unpack_from(b'>L', exth121[0], 0)
                if datain_kf8 == 0xffffffff:
                    self.combo = False
                    return
            datain_kfrec0 = readsection(datain, datain_kf8)

            self.result_file = bytearray(datain)

            # check if there are SRCS records and reduce them
            srcs = getint(datain_rec0, srcs_index)
            num_srcs = getint(datain_rec0, srcs_count)
            if srcs != 0xffffffff and num_srcs > 0:
                for i in range(srcs, srcs + num_srcs):
                    self.result_file = nullsection(self.result_file, i)
                datain_rec0 = writeint(datain_rec0, srcs_index, 0xffffffff)
                datain_rec0 = writeint(datain_rec0, srcs_count, 0)

            if remove_personal_label:
                datain_rec0 = add_exth(datain_rec0, exth_cdetype, b"EBOK");
                exth = read_exth(datain_rec0, exth_asin)
                if len(exth) == 0:
                    datain_rec0 = add_exth(datain_rec0, exth_asin, bytes(to_base(document_id.int, base=32, min_num_digits=10), 'ascii'))
                # exth = read_exth(datain_rec0, exth_cdecontentkey)
                # if len(exth) == 0:
                #     datain_rec0 = add_exth(datain_rec0, exth_cdecontentkey, bytes(to_base(document_id.int, base=32, min_num_digits=10), 'ascii'))

            self.result_file = writesection(self.result_file, 0, datain_rec0)

            firstimage = getint(datain_rec0, first_resc_record)

            # Only keep the correct EXTH 116 StartOffset, KG 2.5 carries over the one from the mobi7 part, which then points at garbage in the mobi8 part, and confuses FW 3.4
            kf8starts = read_exth(datain_kfrec0, 116)
            # If we have multiple StartOffset, keep only the last one
            kf8start_count = len(kf8starts)
            while kf8start_count > 1:
                kf8start_count -= 1
                datain_kfrec0 = del_exth(datain_kfrec0, 116)

            exth_cover = read_exth(datain_kfrec0, exth_cover_offset)
            if len(exth_cover) > 0:
                cover_index, = struct.unpack_from('>L', exth_cover[0], 0)
                cover_index += firstimage
            else:
                cover_index = 0xffffffff

            exth_thumb = read_exth(datain_kfrec0, exth_thumb_offset)
            if len(exth_thumb) > 0:
                thumb_index, = struct.unpack_from('>L', exth_thumb[0], 0)
                thumb_index += firstimage
            else:
                thumb_index = 0xffffffff

            if cover_index != 0xffffffff:
                if thumb_index != 0xffffffff:
                    # make sure embedded thumbnail has the right size
                    cover_image = readsection(datain, cover_index)
                    thumb = BytesIO()
                    im = Image.open(BytesIO(cover_image))
                    im.thumbnail((330, 470), Image.ANTIALIAS)
                    im.save(thumb, format=im.format)
                    self.result_file = writesection(self.result_file, thumb_index, thumb.getvalue())
                else:
                    # if nothing works - fall back to the old trick, set thumbnail to the cover image
                    datain_kfrec0 = add_exth(datain_kfrec0, exth_thumb_offset, exth_cover[0])
                    thumb_index = cover_index

                exth = read_exth(datain_kfrec0, exth_thumbnail_uri)
                if len(exth) > 0:
                    datain_kfrec0 = del_exth(datain_kfrec0, exth_thumbnail_uri)
                datain_kfrec0 = add_exth(datain_kfrec0, exth_thumbnail_uri, bytes('kindle:embed:%s' % (to_base(thumb_index - firstimage, base=32, min_num_digits=4)), 'ascii'))

            if remove_personal_label:
                datain_kfrec0 = add_exth(datain_kfrec0, exth_cdetype, b"EBOK");
                # exth = read_exth(datain_kfrec0, exth_asin)
                # if len(exth) == 0:
                #     datain_kfrec0 = add_exth(datain_kfrec0, exth_asin, bytes(to_base(document_id.int, base=32, min_num_digits=10), 'ascii'))
                exth = read_exth(datain_kfrec0, exth_cdecontentkey)
                if len(exth) == 0:
                    datain_kfrec0 = add_exth(datain_kfrec0, exth_cdecontentkey, bytes(to_base(document_id.int, base=32, min_num_digits=10), 'ascii'))

            self.result_file = writesection(self.result_file, datain_kf8, datain_kfrec0)

        elif format == 'azw3':
            datain = b''
            with open(pathof(infile), 'rb') as f:
                datain = f.read()
            datain_rec0 = readsection(datain, 0)
            ver = getint(datain_rec0, mobi_version)
            self.combo = (ver != 8)
            if not self.combo:
                return
            exth121 = read_exth(datain_rec0, 121)
            if len(exth121) == 0:
                self.combo = False
                return
            else:
                # only pay attention to first exth121
                # (there should only be one)
                datain_kf8, = struct.unpack_from(b'>L', exth121[0], 0)
                if datain_kf8 == 0xffffffff:
                    self.combo = False
                    return

            datain_kfrec0 = readsection(datain, datain_kf8)

            # create the standalone mobi7
            num_sec = getint(datain, number_of_pdb_records, b'H')
            # remove BOUNDARY up to but not including ELF record
            self.result_file7 = deletesectionrange(datain, datain_kf8 - 1, num_sec - 2)
            # check if there are SRCS records and delete them
            srcs = getint(datain_rec0, srcs_index)
            num_srcs = getint(datain_rec0, srcs_count)
            if srcs != 0xffffffff and num_srcs > 0:
                self.result_file7 = deletesectionrange(self.result_file7, srcs, srcs + num_srcs - 1)
                datain_rec0 = writeint(datain_rec0, srcs_index, 0xffffffff)
                datain_rec0 = writeint(datain_rec0, srcs_count, 0)
            # reset the EXTH 121 KF8 Boundary meta data to 0xffffffff
            datain_rec0 = write_exth(datain_rec0, 121, struct.pack(b'>L', 0xffffffff))
            # datain_rec0 = del_exth(datain_rec0,121)
            # datain_rec0 = del_exth(datain_rec0,534)
            # don't remove the EXTH 125 KF8 Count of Resources, seems to be present in mobi6 files as well
            # set the EXTH 129 KF8 Masthead / Cover Image string to the null string
            datain_rec0 = write_exth(datain_rec0, 129, b'')
            # don't remove the EXTH 131 KF8 Unidentified Count, seems to be present in mobi6 files as well

            # need to reset flags stored in 0x80-0x83
            # old mobi with exth: 0x50, mobi7 part with exth: 0x1850, mobi8 part with exth: 0x1050
            # Bit Flags
            # 0x1000 = Bit 12 indicates if embedded fonts are used or not
            # 0x0800 = means this Header points to *shared* images/resource/fonts ??
            # 0x0080 = unknown new flag, why is this now being set by Kindlegen 2.8?
            # 0x0040 = exth exists
            # 0x0010 = Not sure but this is always set so far
            fval, = struct.unpack_from(b'>L', datain_rec0, 0x80)
            # need to remove flag 0x0800 for KindlePreviewer 2.8 and unset Bit 12 for embedded fonts
            fval = fval & 0x07FF
            datain_rec0 = datain_rec0[:0x80] + struct.pack(b'>L', fval) + datain_rec0[0x84:]

            self.result_file7 = writesection(self.result_file7, 0, datain_rec0)

            firstimage = getint(datain_rec0, first_resc_record)
            lastimage = getint(datain_rec0, last_content_index, b'H')
            # print("Old First Image, last Image", firstimage,lastimage)
            if lastimage == 0xffff:
                # find the lowest of the next sections and copy up to that.
                ofs_list = [(fcis_index, b'L'), (flis_index, b'L'), (datp_index, b'L'), (hufftbloff, b'L')]
                for ofs, sz in ofs_list:
                    n = getint(datain_rec0, ofs, sz)
                    # print("n",n)
                    if n > 0 and n < lastimage:
                        lastimage = n - 1
            # print("First Image, last Image", firstimage,lastimage)

            # Try to null out FONT and RES, but leave the (empty) PDB record so image refs remain valid
            for i in range(firstimage, lastimage):
                imgsec = readsection(self.result_file7, i)
                if imgsec[0:4] in [b'RESC', b'FONT']:
                    self.result_file7 = nullsection(self.result_file7, i)

            # mobi7 finished

            # create standalone mobi8
            self.result_file8 = deletesectionrange(datain, 0, datain_kf8 - 1)
            target = getint(datain_kfrec0, first_resc_record)
            self.result_file8 = insertsectionrange(datain, firstimage, lastimage, self.result_file8, target)
            datain_kfrec0 = readsection(self.result_file8, 0)

            # Only keep the correct EXTH 116 StartOffset, KG 2.5 carries over the one from the mobi7 part, which then points at garbage in the mobi8 part, and confuses FW 3.4
            kf8starts = read_exth(datain_kfrec0, 116)
            # If we have multiple StartOffset, keep only the last one
            kf8start_count = len(kf8starts)
            while kf8start_count > 1:
                kf8start_count -= 1
                datain_kfrec0 = del_exth(datain_kfrec0, 116)

            # update the EXTH 125 KF8 Count of Images/Fonts/Resources
            datain_kfrec0 = write_exth(datain_kfrec0, 125, struct.pack(b'>L', lastimage - firstimage + 1))

            # need to reset flags stored in 0x80-0x83
            # old mobi with exth: 0x50, mobi7 part with exth: 0x1850, mobi8 part with exth: 0x1050
            # standalone mobi8 with exth: 0x0050
            # Bit Flags
            # 0x1000 = Bit 12 indicates if embedded fonts are used or not
            # 0x0800 = means this Header points to *shared* images/resource/fonts ??
            # 0x0080 = unknown new flag, why is this now being set by Kindlegen 2.8?
            # 0x0040 = exth exists
            # 0x0010 = Not sure but this is always set so far
            fval, = struct.unpack_from('>L', datain_kfrec0, 0x80)
            fval = fval & 0x1FFF
            fval |= 0x0800
            datain_kfrec0 = datain_kfrec0[:0x80] + struct.pack(b'>L', fval) + datain_kfrec0[0x84:]

            # properly update other index pointers that have been shifted by the insertion of images
            ofs_list = [(kf8_fdst_index, b'L'), (fcis_index, b'L'), (flis_index, b'L'), (datp_index, b'L'), (hufftbloff, b'L')]
            for ofs, sz in ofs_list:
                n = getint(datain_kfrec0, ofs, sz)
                if n != 0xffffffff:
                    datain_kfrec0 = writeint(datain_kfrec0, ofs, n + lastimage - firstimage + 1, sz)

            exth_cover = read_exth(datain_kfrec0, exth_cover_offset)
            if len(exth_cover) > 0:
                cover_index, = struct.unpack_from('>L', exth_cover[0], 0)
                cover_index += target
            else:
                cover_index = 0xffffffff

            exth_thumb = read_exth(datain_kfrec0, exth_thumb_offset)
            if len(exth_thumb) > 0:
                thumb_index, = struct.unpack_from('>L', exth_thumb[0], 0)
                thumb_index += target
            else:
                thumb_index = 0xffffffff

            if cover_index != 0xffffffff:
                if thumb_index != 0xffffffff:
                    # make sure embedded thumbnail has the right size
                    cover_image = readsection(self.result_file8, cover_index)
                    thumb = BytesIO()
                    im = Image.open(BytesIO(cover_image))
                    im.thumbnail((330, 470), Image.ANTIALIAS)
                    im.save(thumb, format=im.format)
                    self.result_file8 = writesection(self.result_file8, thumb_index, thumb.getvalue())
                else:
                    # if nothing works - fall back to the old trick, set thumbnail to the cover image
                    datain_kfrec0 = add_exth(datain_kfrec0, exth_thumb_offset, exth_cover[0])
                    thumb_index = cover_index

                exth = read_exth(datain_kfrec0, exth_thumbnail_uri)
                if len(exth) > 0:
                    datain_kfrec0 = del_exth(datain_kfrec0, exth_thumbnail_uri)
                datain_kfrec0 = add_exth(datain_kfrec0, exth_thumbnail_uri, bytes('kindle:embed:%s' % (to_base(thumb_index - target, base=32, min_num_digits=4)), 'ascii'))

            if remove_personal_label:
                datain_kfrec0 = add_exth(datain_kfrec0, exth_cdetype, b"EBOK");
            else:
                datain_kfrec0 = add_exth(datain_kfrec0, exth_cdetype, b"PDOC");
            # exth = read_exth(datain_kfrec0, exth_asin)
            # if len(exth) == 0:
            #     datain_kfrec0 = add_exth(datain_kfrec0, exth_asin, bytes(to_base(document_id.int, base=32, min_num_digits=10), 'ascii'))
            exth = read_exth(datain_kfrec0, exth_cdecontentkey)
            if len(exth) == 0:
                datain_kfrec0 = add_exth(datain_kfrec0, exth_cdecontentkey, bytes(to_base(document_id.int, base=32, min_num_digits=10), 'ascii'))

            self.result_file8 = writesection(self.result_file8, 0, datain_kfrec0)

            # mobi8 finished

    def getResult(self):
        return self.result_file

    def getResult8(self):
        return self.result_file8

    def getResult7(self):
        return self.result_file7 class mobi_read:
    def __init__(self, infile, width=330, height=470, stretch=False):

        self.asin = ''
        self.cdetype = 'PDOC'
        self.cdecontentkey = ''
        self.acr = ''
        self.thumbnail = None
        self.pagedata = b''

        datain = b''
        with open(pathof(infile), 'rb') as f:
            self.acr = re.sub('[^-A-Za-z0-9 ]+', '_', f.read(32).replace(b'\x00', b'').decode('latin-1'))
            f.seek(0)
            datain = f.read()

        datain_rec0 = readsection(datain, 0)

        exth121 = read_exth(datain_rec0, 121)
        self.combo = True
        if len(exth121) == 0:
            self.combo = False
        else:
            # only pay attention to first exth121
            # (there should only be one)
            datain_kf8, = struct.unpack_from(b'>L', exth121[0], 0)
            if datain_kf8 == 0xffffffff:
                self.combo = False

        # Look for PageMap
        srcs = getint(datain_rec0, first_non_text)
        num_srcs = getint(datain, number_of_pdb_records, b'H')
        if srcs != 0xffffffff and num_srcs > 0:
            for i in range(srcs, srcs + num_srcs):
                data = readsection(datain, i)
                if data[0:4] == b"PAGE":
                    self.pagedata = data

        exth = read_exth(datain_rec0, exth_asin)
        if len(exth) > 0:
            self.asin = exth[0].decode("ascii")
        exth = read_exth(datain_rec0, exth_cdetype)
        if len(exth) > 0:
            self.cdetype = exth[0].decode("ascii")
        exth = read_exth(datain_rec0, exth_cdecontentkey)
        if len(exth) > 0:
            self.cdecontentkey = exth[0].decode("ascii")

        firstimage = getint(datain_rec0, first_resc_record)

        exth_cover = read_exth(datain_rec0, exth_cover_offset)
        if len(exth_cover) > 0:
            cover_index, = struct.unpack_from('>L', exth_cover[0], 0)
            cover_index += firstimage
        else:
            cover_index = 0xffffffff

        exth_thumb = read_exth(datain_rec0, exth_thumb_offset)
        if len(exth_thumb) > 0:
            thumb_index, = struct.unpack_from('>L', exth_thumb[0], 0)
            thumb_index += firstimage
        else:
            thumb_index = 0xffffffff

        if cover_index != 0xffffffff:
            w, h = 0, 0
            if thumb_index != 0xffffffff:
                image = readsection(datain, thumb_index)
                self.thumbnail = Image.open(BytesIO(image))
                w, h = self.thumbnail.size
            if (w < width and h < height) or stretch:
                image = readsection(datain, cover_index)
                self.thumbnail = Image.open(BytesIO(image))
                if stretch:
                    self.thumbnail = self.thumbnail.resize((width, height), Image.LANCZOS)
                else:
                    self.thumbnail.thumbnail((width, height), Image.LANCZOS)

        if self.combo:
            # always try to use information from KF8 part
            datain_kfrec0 = readsection(datain, datain_kf8)
            exth = read_exth(datain_kfrec0, exth_asin)
            if len(exth) > 0:
                self.asin = exth[0].decode("ascii")
            exth = read_exth(datain_kfrec0, exth_cdetype)
            if len(exth) > 0:
                self.cdetype = exth[0].decode("ascii")
            exth = read_exth(datain_kfrec0, exth_cdecontentkey)
            if len(exth) > 0:
                self.cdecontentkey = exth[0].decode("ascii")

    def getACR(self):
        return self.acr

    def getASIN(self):
        return self.asin

    def getCdeType(self):
        return self.cdetype

    def getCdeContentKey(self):
        return self.cdecontentkey

    def getPageData(self):
        return self.pagedata

    def getThumbnail(self):
        return self.thumbnail class PageMapProcessor:

    def __init__(self, data, log):
        self.data = data
        self.log = log
        self.pagenames = []
        self.pageoffsets = []
        self.pageMap = ''
        self.pm_len = 0
        self.pm_nn = 0
        self.pn_bits = 0
        self.pmoff = None
        self.pmstr = ''
        rev_len, = struct.unpack_from(b'>L', self.data, 0x10)
        # skip over header, revision string length data, and revision string
        ptr = 0x14 + rev_len
        pm_1, self.pm_len, self.pm_nn, self.pm_bits  = struct.unpack_from(b'>4H', self.data, ptr)
        # print(pm_1, self.pm_len, self.pm_nn, self.pm_bits)
        self.pmstr = self.data[ptr+8:ptr+8+self.pm_len]
        self.pmoff = self.data[ptr+8+self.pm_len:]
        offsize = b">L"
        offwidth = 4
        if self.pm_bits == 16:
            offsize = b">H"
            offwidth = 2
        ptr = 0
        for i in range(self.pm_nn):
            od, = struct.unpack_from(offsize, self.pmoff, ptr)
            ptr += offwidth
            self.pageoffsets.append(od)
        self.pagenames, self.pageMap = _parseNames(self.pm_nn, self.pmstr, log)

    def getPageMap(self):
        return self.pageMap

    def getNames(self):
        return self.pagenames

    def getOffsets(self):
        return self.pageoffsets

    def generateAPNX(self, apnx_meta):
        if apnx_meta['format'] == 'MOBI_8':
            content_header = '{"contentGuid":"%(contentGuid)s","asin":"%(asin)s","cdeType":"%(cdeType)s","format":"%(format)s","fileRevisionId":"1","acr":"%(acr)s"}' %apnx_meta
        else:
            content_header = '{"contentGuid":"%(contentGuid)s","asin":"%(asin)s","cdeType":"%(cdeType)s","fileRevisionId":"1"}' % apnx_meta
        content_header = content_header.encode('utf-8')
        page_header = '{"asin":"%(asin)s","pageMap":"%(pageMap)s"}' % apnx_meta
        page_header = page_header.encode('utf-8')
        apnx = struct.pack(b'>H',1) + struct.pack(b'>H',1)
        apnx += struct.pack(b'>I', 12 + len(content_header))
        apnx += struct.pack(b'>I', len(content_header))
        apnx += content_header
        apnx += struct.pack(b'>H', 1)
        apnx += struct.pack(b'>H', len(page_header))
        apnx += struct.pack(b'>H', self.pm_nn)
        apnx += struct.pack(b'>H', 32)
        apnx += page_header
        for page in self.pageoffsets:
            apnx += struct.pack(b'>L', page)
        return apnx
import os
import sys
import logging
import tempfile
import subprocess
import argparse
import zipfile
import time
import shutil
import uuid
import version
from slugify import slugify
from modules.utils import get_executable_name, get_executable_path, format_pattern, clean_file_name
from modules.fb2html import Fb2XHTML
from modules.epub import EpubProc
from modules.config import ConverterConfig
from modules.sendtokindle import SendToKindle
from modules.mobi_split import mobi_split, mobi_read
from modules.mobi_pagemap import PageMapProcessor
                        config.log.warning('No information to generate page index')
                except:
                    config.log.warning('Unable to generate page index (APNX)')
                    config.log.debug('Getting details', exc_info=True, stack_info=True)

    config.log.info('Book conversion completed in {0} sec.\n'.format(round(time.clock() - start_time, 2)))

    if config.send_to_kindle['send']:
        if config.output_format.lower() != 'mobi':
            config.log.warning('Kindle Personal Documents Service only accepts personal mobi files')
        else:
            config.log.info('Sending book...')
            try:
                kindle = SendToKindle()
                kindle.smtp_server = config.send_to_kindle['smtpServer']
                kindle.smtp_port = config.send_to_kindle['smtpPort']
                kindle.smtp_login = config.send_to_kindle['smtpLogin']
                kindle.smtp_password = config.send_to_kindle['smtpPassword']
                kindle.user_email = config.send_to_kindle['fromUserEmail']
                kindle.kindle_email = config.send_to_kindle['toKindleEmail']
                kindle.convert = False
                kindle.send_mail([outputfile])

                config.log.info('Book has been sent to "{0}"'.format(config.send_to_kindle['toKindleEmail']))

                if config.send_to_kindle['deleteSendedBook']:
                    try:
                        os.remove(outputfile)
                    except:
                        config.log.error('Unable to remove file "{0}".'.format(outputfile))
                        return

            except KeyboardInterrupt:
                print('User interrupt. Exiting...')
                rm_tmp_files(temp_dir)
                sys.exit(-1)

            except:
                config.log.error('Error sending file')
                config.log.debug('Getting details', exc_info=True, stack_info=True)

    # Чистим временные файлы
    rm_tmp_files(temp_dir)
    return os.path.splitext(outputfile)[0] + '.' + ext


def process_folder(config, inputdir, outputdir=None):
    if outputdir:
        if not os.path.exists(outputdir):
            os.makedirs(outputdir)

    if os.path.isdir(inputdir):
        for root, _, files in os.walk(inputdir):
            for file in files:
                try:
                    if file.lower().endswith(('.fb2', '.fb2.zip', '.zip', '.epub')):
                        inputfile = os.path.join(root, file)
                        # Обработка каталога. Смотрим признак рекурсии по подкаталогам
                        if (not config.recursive and inputdir == root) or config.recursive:
                            process_file(config, inputfile, None)
                            if config.delete_source_file:
                                try:
                                    os.remove(inputfile)
                                except:
                                    config.log.error('Unable to remove file "{0}"'.format(inputfile))

                            continue
                except KeyboardInterrupt as e:
                    print('User interrupt. Exiting...')
                    sys.exit(-1)

                except IOError as e:
                    config.log.error('(I/O error {0}) {1} - {2}'.format(e.errno, e.strerror, e.filename))
                except:
                    config.log.error('Error processing folder')
                    config.log.debug('Getting details', exc_info=True, stack_info=True)

    else:
        config.log.critical('Unable to find directory "{0}"'.format(inputdir))
        sys.exit(-1)


def get_log_level(log_level):
    if log_level.lower() == 'info':
        return logging.INFO
    elif log_level.lower() == 'error':
        return logging.ERROR
    elif log_level.lower() == 'critical':
        return logging.CRITICAL
    elif log_level.lower() == 'debug':
        return logging.DEBUG
    else:
        return logging.INFO


def process(myargs):
    infile = myargs.infile
    outfile = myargs.outfile

    application_path = get_executable_path()
    config_file_name = "{0}.config".format(get_executable_name())

    if myargs.config_file:
        if os.path.exists(myargs.config_file):
            # full path is given
            config_file = myargs.config_file
        elif os.path.exists(os.path.join(application_path, myargs.config_file)):
            # found relative to program directory
            config_file = os.path.join(application_path, myargs.config_file)
        else:
            print('Unable to locate configuration file "{0}"'.format(myargs.config_file))
            sys.exit(-1)
    else:
        config_file = os.path.join(os.path.expanduser('~'), 'fb2mobi' if version.WINDOWS else '.fb2mobi', config_file_name)
        if not os.path.exists(config_file):
            # last resort - see if we have default config in program directory
            if os.path.exists(os.path.join(application_path, config_file_name)):
                config_file = os.path.join(application_path, config_file_name)

    # if configuration does not exist - it will be created in user HOME directory

    config = ConverterConfig(config_file)