Программа для извлечения и замены иллюстраций в .fb2-книгах

(зеркала: http://www.flibusta.net/node/83100 и http://www.the-ebook.org/forum/viewtopic.php?t=15947)

Дарю обществу: fb2bin - извлекалка/заменялка двоичных вложений (картинок, MIDI-файлов и прочего) в .fb2-файлах. Возможности:

  • проверка целостности вложений и вывод их списка с размерами в байтах;
  • извлечение вложений в виде файлов в указанный каталог;
  • замена вложений на имеющиеся в указанном каталоге файлы.

В ближайших планах - обработка вложений не сплошная, а списком; сличение вложений с файлами; обработка вложений .bat-файлами или иными скриптами.
Программа запускается с командной строки, может принимать и отдавать FB2-файлы через стандартный ввод/вывод (т.е. использоваться как фильтр). При запуске без параметров даёт подсказку о способе вызова подсказки :-) - правда, по-английски; даю краткий перевод:

Цитата:
fb2bin - список/извлечение замена двоичных вложений в FB2-файлах, (версия, дата)
Вдохновлено форумом о программах для "ебуководов"
Использование:
fb2bin -h
fb2bin -l [ -vqfi ] fb2-файл
fb2bin -x [ -vqfid путь ] исходный-fb2 [ id-вложения ... ] [ @список-id'ов-вложений ... ]
fb2bin -r [ -vqfd путь ] исходный-fb2 выходной-fb2 [ id-вложения ... ] [ @список-id'ов-вложений ... ]
Команды:
-h, --help - подсказка
-l, --list - показать список вложений
-x, --extract - извлечь вложения в файлы
-r, --replace - заменить вложения на файлы
Аргументы:
исходный-fb2, выходной-fb2 - имена файлов, либо "-" - входной и выходной поток соответственно
id-вложения - один или более идентификаторов извлекаемых/заменяемых вложений (если не указано - будут обрабатываться все вложения)
список-id'ов-вложений - один или более файлов, содержащих идентификаторы обрабатываемых вложений
Опции:
-q, --quiet - режим "молчун" (выдаются только сообщения об ошибках; удобно для батничков; если задать дважды - будет вообще ничего не сообщаться)
-v --verbose - режим "болтун" (полезно, когда что-то идёт не так и хочется узнать причины происходящего; если задать дважды - будет ещё болтливее)
-f, --far-extension - режим расширенной поддержки Far Manager'а (http://www.farmanager.com)
-i, --ignore-invalid - игнорировать невалидность description'а исходного FB2 (может вызвать странные глюки)
-d, --base-dir=путь - каталог, куда складывать / откуда брать файлы (по умолчанию - текущий каталог)
Коды ERRORLEVEL (для использования в .bat-файлах):
0 - всё сделано ОК;
1 - всё сделано, но возможно, что выходной(-ые) файл(ы) невалиден(-ны)
2 - невалидный исходный файл - выполнение невозможно
3 - приключилась фатальная ошибка (скорее всего - нехватка памяти либо места на диске, но может быть и ошибка лично моя; имеет смысл попробовать ещё раз с увеличенным уровнем "болтливости", если всё равно непонятно - спросить у меня)
4 - ошибка в параметрах командной строки.

Пример применения - для замены прозрачных картинок в FB2-книге на обычные:
Цитата:
@echo off
: извлекаем:
fb2bin -x -d . Kalma_Sirotyi_kvartala_Belvill.192080.fb2
: убираем не-png'шное:
del *.jpg
: делаем непрозрачные:
limpng -r *.png
: возвращаем первоначальные имена:
del ?????.png
ren *.png ?????.*
: минимизируем размер файлов (в отдельный подкаталог):
optipng -O7 -d opti\ *.png
: загоняем обратно в FB2:
fb2bin -r -d opti\ Kalma_Sirotyi_kvartala_Belvill.192080.fb2 - >kalma.fb2

Ну и, само собой, исходники тоже лежат - вдруг кому-то пригодится. Кто найдёт ошибку или предложит доработку - делитесь! :-)
18/10/2010: выложена версия 1.1.
Исправлена пара мелких, но неприятных ошибок. Подправлен интерфейс, улучшен вид выходного FB2-документа (аккуратнее сделаны отступы при изменяемых полях), заменён движок на более компактный и шустрый - программа "похудела" в тридцать раз, с 600 до 20 килобайт - не пугайтесь, это не вирус! :-)
Как обычно - с исходниками.
19/10/2010: версия 1.2.
Добавлена поддержка неюникодных кодировок (в т.ч. windows-1251) - оказывается, XML-движок их не поддерживал. Теперь поддерживает - я его переделал. Правда, без перекодировки - изменённый файл сохраняется в той же кодировке, что и исходный. Извиняюсь за невнимательность, скачавших предыдущие версии прошу скачать свежую.
28/10/2010: версии 1.3 (к вечеру уже устарела :-( ) и 1.4.
Исправлена мелкая ошибка с порядком разбора тэгов; добавлена опция "не проверять валидность description'а FB2-документа" при просмотре списка вложений и их выгрузке; добавлена возможность выборочной обработки вложений с целью интеграции с Far Manager'ом; добавлен режим полного подавления вывода сообщений.
02/11/2010 - версия 1.5
  • Добавлена возможность передачи id'ов обрабатываемых вложений списковыми файлами - гл. обр. для сокращения количества запусков программы при массированных обработках и для удобства использования с Far'ом;
  • расширена поддержка Far Manager'а - пока это только генерация псевдовложения с id="dirinfo" и вывод в него ID'а документа и номера версии (в планах - псевдовложение с id="files.bbs" или "descript.ion" с описаниями картинок (из атрибута title тэга image, из следующего за иллюстрацией текста и т.д.); если кто захочет - добавлю вывод ещё чего-ньдь полезного);
  • в поставку включен файл custom_user.ini - пример использования программы в качестве архиватора для Far'а.

Комментарии

Вот почему тебя давно не видно было!)).
скачал, посмотрю. Оболочку делать будешь? Если хочешь, чтобы программа использовалась обществом - нужно, имхо

wotti написал:
Оболочку делать будешь?
А зачем? Получится "однокнопочный конвертер" - те же уши, только в профиль и красиво размалёванные. :)
А вот где бы GUI'ёвая оболочка не помешала - это чтобы можно было каждую картинку в отдельности заменять и каждый раз смотреть - что получилось; но тут как раз проще будет не на сях писать, а доваять скриптец к тому же FBE - а я в ём не силён... :-(

Насчёт такого скрипта уже мысль такая была, так, что всё возможно ))

Заманался воевать с несколькими глюками из libxml2, переделываю на MiniXML - но у него тоже свои мухи. :-(
Если кто ещё знает какие маленькие и послушные сишные опен-сорсные библиотеки для возни с XML - делитесь!
Upd: можно не искать - Mini-XML (http://www.minixml.org/) годится; с мухами справился и подпатчил обработку entities'ов на свой вкус - за вечер; полдесятка использованных функций добавили к объёму готового exe'шника всего пару килобайт; короче - рекомендую! :-)

Выложил свежую версию - 1.1.
Исправлена пара мелких, но неприятных ошибок. Подправлен интерфейс, улучшен вид выходного FB2-документа (аккуратнее сделаны отступы при изменяемых полях), заменён движок на более компактный и шустрый - программа "похудела" в тридцать раз, с 600 до 20 килобайт - не пугайтесь, это не вирус! :-)

полезная утилита, спасибо.
а нельзя ли добавить опциональную возможность извлечения отдельного заданного файла?
это позволило бы более тесно интегрировать утилиту с Far manager

ccaid написал:
интегрировать утилиту с Far manager
О, это идея: fb2bin в качестве "архиватора" для FB2-"архивов".
ccaid написал:
а нельзя ли добавить опциональную возможность извлечения отдельного заданного файла?
Не проблема, добавлю. Имена вложений будут перечисляться после имени FB2-файла - как в настоящих архиваторах, ОК? Но тогда с именем нельзя задавать путь, т.к. буду искать на точное совпадение; путь придётся задавать через ключ "-d".

Рыжий Тигра написал:
Имена вложений будут перечисляться после имени FB2-файла - как в настоящих архиваторах, ОК? Но тогда с именем нельзя задавать путь, т.к. буду искать на точное совпадение; путь придётся задавать через ключ "-d".
нормально.

ccaid написал:
Рыжий Тигра написал:
Имена вложений будут перечисляться после имени FB2-файла [...] путь придётся задавать через ключ "-d".
нормально.
Выгружалка готова, доделаю заменялку и к полуночи по MSK выложу. Давай обвязку для FAR'а - хочу сразу глянуть, как оно! :-)

Рыжий Тигра написал:
Давай обвязку для FAR'а - хочу сразу глянуть, как оно! :-)

custom_user.ini написал:
[fb2]
Extension=fb2
List="fb2bin -l %%AQ"
Errorlevel=1
Start=""
End=""
Format0="/^\s*(?P[\d]+)\s(?P.*?)\s*$/"
Extract=fb2bin -x %%AQ %%F
ExtractWithoutPath=fb2bin -x %%AQ %%F
Test=
Delete=
Comment=
CommentFiles=
SFX=
Lock=
Protect=
Recover=
Add=fb2bin -r %%AQ %%AQ.fb2 %%F
Move=
AddRecurse=
MoveRecurse=
AllFilesMask=

предназначено для Far2
сохранить в Plugins\Multiarc\Formats\custom_user.ini (добавить в существующий или создать новый)
при замене вложения создается новый файл, имя которого образуется добавлением постфикса ".fb2"
если желательно, чтобы замена вложения происходила без создания нового файла, надо убрать этот постфикс из строки Add=

ccaid написал:
предназначено для Far2
Пытался запустить с Far 1.75 build 2632 - командная строка формируется правильно (успеваю засечь в заголовке окна Far'а), но до запуска fb2bin дело не доходит (проверил - в начале main'а вызываю DebugBreak()). fb2bin.exe в path'е. Где собака порылась?
Upd: Нашёл: именно DebugBreak() и подосрал - fb2bin на нём падал, почему-то не перехватываясь отладчиком. :-(
Из нехорошестей - в stdout после списка попадает строка диагностики. Ща исправлю.

Аддон к MultiArc для TotalCommander-a:

[FB2]
; -----------------------------------------------------------------------
; fb2bin - извлекалка/заменялка двоичных вложений в .fb2-файлах.
; URL: sourceforge.net/projects/fb2bin/
; Author: Рыжий Тигра
; -----------------------------------------------------------------------
Description="List, extract and replace binary attachments in FB2 files."
Extension=fb2
Archiver=%COMMANDER_PATH%\plugins\wcx\arc\fb2bin.exe
List=%PQA -l %AQ
End="^FB2 scanned; "
Format0="zzzzzzz nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
Extract=%PQA -x %AQ
ExtractWithPath=%PQA -x %AQ
Add=%PQA -r %AQ %AQ
; -----------------------------------------------------------------------

Barracuda написал:
Аддон к MultiArc для TotalCommander-a:
Сенькс! Но тотал-коммандер в наших местностях не в чести, у меня, в частности, нет и опробовать не могу. Но в следующую версию включу. (См. также custom_user.ini для Far'а в fb2bin-1.5.7z - насчёт передачи имён вложений списком; неплохо бы и инструкцию по установке адд-она...)

Подновил аддон к TotalCommander-у:

[FB2]
; -----------------------------------------------------------------------
; fb2bin - извлекалка/заменялка двоичных вложений в .fb2-файлах.
; URL: sourceforge.net/projects/fb2bin/
; Forum: lib.rus.ec/node/244581
;        flibusta.net/node/83100
;        the-ebook.org/forum/viewtopic.php?t=15947
; Author: Рыжий Тигра
; -----------------------------------------------------------------------
Description="List, extract and replace binary attachments in FB2 files."
Extension=fb2
Archiver=%COMMANDER_PATH%\plugins\wcx\arc\fb2bin.exe
List=%PQA -l %AQA
End="^FB2 scanned; "
Format0="zzzzzzz nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
Extract=%PQA -x %AQA @%LQA
ExtractWithPath=%PQA -x %AQA @%LQA
Add=%PQA -r %AQA %AQA @%LQA
; -----------------------------------------------------------------------

Рыжий Тигра написал:
...неплохо бы и инструкцию по установке адд-она...

Это можно...
Инструкция:
1. Добавляем вышеприведённую секцию в Multiarc.ini, исправив в строке Archiver= путь к fb2bin.exe на свой.
2. В Тотале в окне Настройка на вкладке Архиваторы жмём кнопку Настройка архиваторных плагинов. В открывшемся окне в поле Файлы с расширением: вбиваем расширение fb2, а затем в списке плагинов выбираем multiarc.wcx. После этого, если не хотим чтобы у fb2-файлов были дефолтные архивные иконки и хотим входить в FB2-файл по Ctrl+PgDn, а не по даббл-клику, в поле над списком плагинов (слева от кнопки Обзор) число перед путём к multiarc.wcx меняем на 287.

Под линухом - несобирается.
Почистил, что мог, конечно... Задефайнил ещё кучку (обратный слеш, например).
Но хочет, видимо, struct _stat - что бы это могло быть?

Нельзя ли к POSIX привести?

libmxml, вроде, подхватился... не патченный, понятно - из репы.
патчить либу - плохо :/

Оно ж, вроде, консольное - можно вполне в POSIX уложиться.
Ну, в ifdef/endif можно экстрасы добавить для учёта, скажем, файловой системы "с буквами дисков"...

jno написал:
Под линухом - несобирается.
Покажи вывод компилятора/линкера. И/или можно ещё припрячь Anarchist'а - он над другой моей прогой брался издеваться... :-)
jno написал:
struct _stat - что бы это могло быть?
Насколько помню libc - struct stat.
jno написал:
Нельзя ли к POSIX привести?
С POSIX'ом в чистом виде дела, увы , не имел. :-( Где можно посмотреть POSIX'ные библиотеки файловых операций?
jno написал:
libmxml [...] патчить либу - плохо :/
А куда я денусь? :-( В чистом виде неработоспособна в конкретных условиях (см. ф-цию _mxml_global() в статической библиотеке) и/или не делает то, что мне нужно (например, не рюхает .fb2, сделанные в CP1251). И потом, зачем она в сырцах - чтобы ими любоваться? :-)
jno написал:
в ifdef/endif можно экстрасы добавить для учёта, скажем, файловой системы "с буквами дисков"...
Можно. Но руки не доходят и линукса под руками нет. :-( Доведёшь?

Пилять! №;%:?*( коннективити любимого либрусека об коленку :-E

Хм-хм... там, похоже, не доводить, а капитально так редезайнить надо.

Во-первых, какая версия сорцов нынче считается кошерной? А то я ухватил, "что с краю лежало"...
Во-вторых, нехудо было бы какой-нибудь Makefile сгенерить (ЕМНИМС, виндовые IDE это позволяли. лет 10 тому как), который уже можно подправить.
В-третьих, опенсурс там или нет, а патченая либа - криво. потому как сорец, считай, придётся дистрибутить вместе с определённой версией либы, к которой, собссно, сделан патч. что есть запредельная кривизна с т.з. мейнтенанса. Лучше д
елать враппер поверх штатной либы.
В-четвёртых, подпил кода под специфику кодировки - тоже идея не фонтан. Есть iconv - вполне себе рабочая феня, все кодировки знает. Я бы вообще всё форсил в юникод - неужто где-то его ещё не переваривают?..

На счёт POSIX - есть рулёзная книжка :)
(книжку скачал, переложил на onlinedisk (пароль - мой ник, от греха...), но на мой вкус какчество - то ещё)
М. И. Беляков, Ю. И. Рабовер, А. Л. Фридман. Мобильная операционная система

Я тут ещё неделю на больничном - могу помочь :)

jno написал:
нехудо было бы какой-нибудь Makefile сгенерить (ЕМНИМС, виндовые IDE это позволяли. лет 10 тому как), который уже можно подправить.
У визуал-студи есть такая фишка, но а толку? Один фиг - сгенерённый ею makefile ничем, кроме опять же ейного make'а, не выполняется, но для загрузки обратоно в IDE уже не годится... :-( Впрочем, если хочешь, могу вкладывать в .src'ы ещё и .mak, .cod и .map-файлы
jno написал:
а патченая либа - криво. потому как сорец, считай, придётся дистрибутить вместе с определённой версией либы, к которой, собссно, сделан патч.
Это да, есть такое. :-( Но один фиг иначе никак. :-(
jno написал:
Лучше делать враппер поверх штатной либы.
Не получится - изменения касаются слишком глубоколежащих функций.
jno написал:
подпил кода под специфику кодировки - тоже идея не фонтан. Есть iconv
Не под специфику каждой конкретной кодировки, а под (игнорируемый текущим "официальным" mxml'ом) факт, что разные кодировки таки бывают. Обрати внимание - я не определяю, какая именно кодировка, я только выясняю, что это ни фига не UTF-8.
jno написал:
Я бы вообще всё форсил в юникод - неужто где-то его ещё не переваривают?..
Здесь, например. Ни чистым юникодом, ни его производными UTF-16xx никто не заморачивается - только или UTF-8, или 1251.
jno написал:
На счёт POSIX [...] неделю на больничном - могу помочь :)
Всё равно не врубаюсь - какие не-POSIX'ные куски fb2bin'а можно переделать под POSIX, чтобы роботоспособность под win32 тоже сохранилась? Если не трудно - покажи на примерах.

Рыжий Тигра написал:
jno написал:
С POSIX'ом в чистом виде дела, увы , не имел. :-( Где можно посмотреть POSIX'ные библиотеки файловых операций?

ещё можно штатными манами воспользоваться:
http://linux.die.net/man/2/open
http://www.manpagez.com/man/2/open/

кроме того open(2), смотри chmod(2), stat(2), fstat(2), fopen(3), readdir(3)...

традиционно в скобках - номер секции мана (2 - системный вызов, 3 - библиотека)...

Да! Ещё один момент - в POSIX-оболочках (типа линухового bash) глоббинг всяких метасимволов командной строки (типа *) выполняется оболочкой, а не программой!

Т.е. для того, чтобы обработать вызов вида myProg all*such*files.?????, в ДОСе/форточках надо самому выполнить поиск нужных файлов, а в POSIX - просто пройтись по argv.

Такие фени надо оформлять в платформенно-специфичные секции в ifdef/endif

jno написал:
в POSIX-оболочках (типа линухового bash) глоббинг всяких метасимволов командной строки (типа *) выполняется оболочкой, а не программой!
Т.е. для того, чтобы обработать вызов вида myProg all*such*files.?????, в ДОСе/форточках надо самому выполнить поиск нужных файлов, а в POSIX - просто пройтись по argv.
Для DOS'а не знаю, а для MSVC6 достаточно добавить setargv.obj линкеру в список библиотек и модулей - и разбор "джокеров" будет выполнен до вызова main'а.

Выложил версию 1.4 (см. историю изменений в стартовом посте), привёл описание в соответствие с реальностью. :)

Тигра, а поясни мне темному в двух словах, пожалуйста, какая основная идея этой программы.
Ведь то, что перечислено в самом начале, т.е. проверка, извлечение и замена делается в FB Editor'e и вполне удовлетворительно, вроде как. Или я чего-то не понял?

s_Sergius написал:
какая основная идея этой программы. [...] проверка, извлечение и замена делается в FB Editor'e и вполне удовлетворительно
Но трахоёмко, т.е. вручную. При этом легко перепутать порядок картинок, ничто не мешает вставить не туда, где было, или забыть заменить нужную или удалить лишнюю... :-(
У меня ручная замена полусотни картинок занимает около часа. А если нужно посмотреть, что получилось, по результатам внести изменения в картинки и снова заменять и пробовать, то это уже даже не напрягает, а конкретно раздражает.
Другое дело - автоматическая массовая замена. Но такого скрипта я не нашёл - есть только выгрузка оптом.
И наконец, если нужно забабахать обработку картинок "не прикладая рук" (к примеру, для конвертирования FB2 в что-нибудь ещё или под какой-то специфический девайс, да ещё и на удалённом сервисе) - то FBE2 вообще не годится никаким боком...

Или у нас разные FBE или одно из двух.
Для массовой обработки есть скрипты "Сохранить объекты" и "Удалить все вложения". При этом сами картинки из fb2 удаляются, а ссылки-то остаются.
Далее обрабатываем картинки любыми средствами, хоть прикладая руки, хоть нет.
А дальше через обычную "скрепочку" (прикрепить бинарный файл) за один раз выбираем ВСЕ картинки и вкладываем назад. Всё. Остается только заново указать обложку. Ну и вызвать скрипт "Обновление картинок", если нужно.
Таким образом у меня выгрузка, пакетная обработка и обратная загрузка сотни и больше картинок занимает несколько минут.
Ничего особенно трахоемного не нахожу.

А если над набором из пары десятков книг?
А над "пачкой" либрусечного "обновления"?

jno написал:
А если над набором из пары десятков книг?
А над "пачкой" либрусечного "обновления"?

А, дошло. Сразу для кучи книг. Ну если только для этого.
Хотя мне трудно представить, что можно захотеть сделать одним чохом с картинками со всей "пачки" либрусечного "обновления".

Да хотя бы отоптимайзить PNGшки!
Через тот же optipng -full

jno написал:
optipng -full
"-full" явно лишний - только засоряет консоль. А вот "-O7" полезен! :-)

s_Sergius написал:
jno написал:
А если над набором из пары десятков книг?
А над "пачкой" либрусечного "обновления"?

А, дошло. Сразу для кучи книг. Ну если только для этого.
Хотя мне трудно представить, что можно захотеть сделать одним чохом с картинками со всей "пачки" либрусечного "обновления".

s_Sergius написал:
Так я же не против.
Просто для себя уяснил, что утилита хороша для обработки нескольких книг за раз, а для работы с одной можно и обойтись и FBE.

Мне тоже кажется, что ни к чему.
Ну допустим даже вытащишь картинки из кучи файлов. А обратно запихивать куда будешь?!! Всё ж перезабудешь!
Или по принципу «кидай все картинки во все файлы сразу?»
IMHO,

эта утилита больше для пингвинов-линуксоидов, которые не хотят юзать FBE, но при этом хотят работать с картинками в FB2.
(файл FB2 это обычный текстовый файл с закодированными картинками и его можно править в любом текстовом редакторе, затруднение было только в картинках)

Zadd написал:
Ну допустим даже вытащишь картинки из кучи файлов. А обратно запихивать куда будешь?!! Всё ж перезабудешь!
Нууу... можно по подкаталогам разложить, например. Или не начинать обработку следующей э-книги, пока не доделал предыдущую.
Zadd написал:
не хотят юзать FBE, но при этом хотят работать с картинками в FB2. [...] затруднение было только в картинках)
Есть ещё одна вещь: извлечение/замена иллюстраций - дело очень длительное, если иллюстраций в книге за десяток мегабайт; особо противно само открытие такого файла в FBE2, когда комп десятки минут пыхтит и непонятно - редактор ещё работает или уже давно завис. Можно, конечно, потратиться и купить комп с запасом ОЗУ и более мощным процом, но ИМХО применить узкоспециализированную софтинку будет более правильным подходом. :-)
Короче: не нужно - не юзай, юзаешь - не обзывай ненужным. :-)

Нууу... тоже способ. Если б ты про "скрепочку" сказал три недели назад, а "сохранить вложения" и "удалить вложения" позволяли бы выбирать, что именно сохранить и куда, а FBE2 работал бы под любым браузером и под любой ОС и принимал бы командную строку - может, я б и не делал fb2bin (хотя и не факт: всё ж приятнее запускать 20-килобайтную прогу, чем ждать десяток минут. пока FBE2 откроет восьмимеговый файл с тремя сотнями картинок). А сейчас уже поздно - люди пользуются... :-)

я там выше накидал комментов и в личку нагадил :)

Рыжий Тигра написал:
Если б ... "сохранить вложения" и "удалить вложения" позволяли бы выбирать, что именно сохранить и куда...

Дык у каждой картинки есть кнопочки "сохранить", "просмотреть" и "удалить".
Для выборочного сохранения и удаления самое то. И что именно и куда. А после обработки опять же "скрепочка".

Цитата:
А сейчас уже поздно - люди пользуются... :-)

Так я же не против.
Просто для себя уяснил, что утилита хороша для обработки нескольких книг за раз, а для работы с одной можно и обойтись и FBE.

поддержу Тигру - неинтерактивная тулзовина для пакетного преобразования есть весчь!
особенно, если оно ещё и кроссплатформенное (а это для "консольного" приложения - не особо сложно).
можно, например, веб-сервис сделать на основе такой программы - закачал книжку, скачал конвертированную...
опять же, к такой тулзе приделать ГУЙ по вкусу - можно (а обратное - неверно).

Вот, что у меня получилось на ту же тему...

На Питоне, ясен пень. Питон 2.7 - в штатной конфигурации, без добавок.

Умеет показывать, вынимать и заменять бинарные объекты. Для замены - извлеките (--extract) те, что есть, замените файлы "ин плейс" (без переименования) и выполните команду с ключиком --replace. Извлекаются (и заменяются) объекты с теми именами, что прописаны в файле ФБ2.

Код - здесь.

<font size="2" face="Consolas, Courier New, Courier, Monospace" color="black"><small><a href="http://s-c.me/9649/s">Copy&nbsp;Source</a>&nbsp;|&nbsp;<a href="http://s-c.me/9649/h">Copy&nbsp;HTML</a></small><ol><li><font color="#696969">#!/usr/bin/python&nbsp;-Ou</font></li>
<li><font color="#696969">#&nbsp;-*-&nbsp;coding:&nbsp;utf-8&nbsp;-*-</font></li>
<li>&nbsp;</li>
<li><font color="#0000ff">import</font> sys, getopt, base64</li>
<li><font color="#0000ff">from</font> <b>xml</b>.etree.ElementTree <font color="#0000ff">import</font> ElementTree</li>
<li>&nbsp;</li>
<li>do_extract = False</li>
<li>do_replace = False</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">load</font>(fname,size=None):</li>
<li>&nbsp;&nbsp;fp = None</li>
<li>&nbsp;&nbsp;<font color="#0000ff">try</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;fp = <b>open</b>(fname)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">if</font> size <font color="#0000ff">is</font> None :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = fp.read()</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">else</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = fp.read(size)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;fp.close()</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">return</font> text</li>
<li>&nbsp;&nbsp;<font color="#0000ff">except</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">try</font>: fp.close()</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">except</font>: <font color="#0000ff">pass</font></li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">save</font>(fname,value):</li>
<li>&nbsp;&nbsp;fp = None</li>
<li>&nbsp;&nbsp;<font color="#0000ff">try</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;fp = <b>open</b>(fname,<font color="#008000">'w'</font>)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;fp.write(value)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;fp.close()</li>
<li>&nbsp;&nbsp;<font color="#0000ff">except</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">try</font>: fp.close()</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">except</font>: <font color="#0000ff">pass</font></li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">get_encoding</font>(fname):</li>
<li>&nbsp;&nbsp;text = <font color="#cc6633">load</font>(fname,<font color="#008000">1024</font>)</li>
<li>&nbsp;&nbsp;i = text.find(<font color="#008000">'&lt;?xml'</font>)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">if</font> i &lt; <font color="#008000">&nbsp;0</font> :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> &gt;&gt;<b>sys</b>.stderr, <font color="#008000">'No&nbsp;valid&nbsp;XML&nbsp;in'</font>, fname</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<b>sys</b>.exit(<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;j = text.find(<font color="#008000">'?&gt;'</font>,i+<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">if</font> i &lt; <font color="#008000">&nbsp;0</font> :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> &gt;&gt;<b>sys</b>.stderr, <font color="#008000">'No&nbsp;valid&nbsp;XML&nbsp;in'</font>, fname</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<b>sys</b>.exit(<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;text = text[i+<font color="#008000">2</font>:j]</li>
<li>&nbsp;&nbsp;i = text.find(<font color="#008000">'encoding='</font>)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">if</font> i &lt; <font color="#008000">&nbsp;0</font> : <font color="#0000ff">return</font> <font color="#008000">'UTF8'</font></li>
<li>&nbsp;&nbsp;text = text[i+<font color="#008000">9</font>:]</li>
<li>&nbsp;&nbsp;<font color="#0000ff">if</font> text <font color="#0000ff">and</font> text[<font color="#008000">&nbsp;0</font>] <font color="#0000ff">in</font> (<font color="#008000">'"'</font>,<font color="#008000">"'"</font>) :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;c,text = text[<font color="#008000">&nbsp;0</font>],text[<font color="#008000">1</font>:]</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;i = text.find(c)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;text = text[:i]</li>
<li>&nbsp;&nbsp;<font color="#0000ff">else</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;text = text.split().pop(<font color="#008000">&nbsp;0</font>)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">return</font> text</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">traversal</font>(root,level=<font color="#008000">&nbsp;0</font>):</li>
<li>&nbsp;&nbsp;indent = <font color="#008000">'.&nbsp;'</font>*level</li>
<li>&nbsp;&nbsp;<font color="#0000ff">for</font> e <font color="#0000ff">in</font> root:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;tag = e.tag.split(<font color="#008000">'}'</font>).pop(-<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">if</font> tag == <font color="#008000">'binary'</font> :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value = <b>base64</b>.b64decode(e.text)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> indent,tag,(e.text <font color="#0000ff">is</font> None) <font color="#0000ff">and</font> <font color="#008000">'x'</font> <font color="#0000ff">or</font> <b>len</b>(value),e.attrib</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">if</font> do_extract :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#cc6633">save</font>(e.attrib[<font color="#008000">'id'</font>],value)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">elif</font> do_replace :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value = <font color="#cc6633">load</font>(e.attrib[<font color="#008000">'id'</font>])</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.text = <b>base64</b>.b64encode(value)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">elif</font> tag == <font color="#008000">'image'</font> :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> indent,tag,(e.text <font color="#0000ff">is</font> None) <font color="#0000ff">and</font> <font color="#008000">'x'</font> <font color="#0000ff">or</font> <b>len</b>(e.text),e.attrib</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#cc6633">traversal</font>(e,level+<font color="#008000">1</font>)</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">handle</font>( fname ):</li>
<li>&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'File'</font>,fname,</li>
<li>&nbsp;&nbsp;fenc = <font color="#cc6633">get_encoding</font>(fname)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'encoding&nbsp;is'</font>, `fenc`</li>
<li>&nbsp;&nbsp;tree = ElementTree()</li>
<li>&nbsp;&nbsp;root = tree.parse(fname)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">print</font> root.tag,<b>len</b>(root.text)</li>
<li>&nbsp;&nbsp;<font color="#cc6633">traversal</font>(root)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">if</font> do_replace :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;tree.write(fname+<font color="#008000">'.NEW'</font>,encoding=fenc)</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">main</font>():</li>
<li>&nbsp;&nbsp;<font color="#0000ff">try</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">try</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;opts, args = <b>getopt</b>.<b>getopt</b>(</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>sys</b>.argv[<font color="#008000">1</font>:],</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">'?hxr'</font>,</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;(<font color="#008000">'help'</font>,<font color="#008000">'extract'</font>,<font color="#008000">'replace'</font>)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">except</font> <b>getopt</b>.error, why:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> &gt;&gt;<b>sys</b>.stderr, <b>sys</b>.argv[<font color="#008000">&nbsp;0</font>],<font color="#008000">':'</font>,why</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">return</font> <font color="#008000">1</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">else</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">for</font> o,v <font color="#0000ff">in</font> opts :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">if</font> o <font color="#0000ff">in</font> (<font color="#008000">'-h'</font>,<font color="#008000">'-?'</font>,<font color="#008000">'--help'</font>):</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> <b>sys</b>.argv[<font color="#008000">&nbsp;0</font>],<font color="#008000">'[-r|--replace&nbsp;|&nbsp;-x|--extract]&nbsp;&lt;fb2-files...&gt;'</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">return</font> <font color="#008000">&nbsp;0</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">elif</font> o <font color="#0000ff">in</font> (<font color="#008000">'-x'</font>,<font color="#008000">'--extract'</font>):</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">global</font> do_extract ; do_extract = True</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">elif</font> o <font color="#0000ff">in</font> (<font color="#008000">'-r'</font>,<font color="#008000">'--replace'</font>):</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">global</font> do_replace ; do_replace = True</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">pass</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">if</font> do_extract <font color="#0000ff">and</font> do_replace :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> &gt;&gt;<b>sys</b>.stderr,<font color="#008000">'Don`t&nbsp;extract&nbsp;and&nbsp;replace&nbsp;at&nbsp;the&nbsp;same&nbsp;time!'</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">return</font> <font color="#008000">1</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">for</font> arg <font color="#0000ff">in</font> args :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#cc6633">handle</font>( arg )</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">return</font> <font color="#008000">&nbsp;0</font></li>
<li>&nbsp;&nbsp;<font color="#0000ff">finally</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">pass</font></li>
<li>&nbsp;</li>
<li><font color="#0000ff">if</font> __name__==<font color="#008000">'__main__'</font> :</li>
<li>&nbsp;&nbsp;<b>sys</b>.exit( <font color="#cc6633">main</font>() )</li>
<li><font color="#696969">#&nbsp;vim:ai:sts=2:et</font></li>
<li><font color="#696969">#&nbsp;EOF&nbsp;#&nbsp;</font></li>
</ol></font>

jno написал:
Вот, что у меня получилось на ту же тему...
На Питоне, ясен пень.
Ага. А в изменённой книге продвинуть версию и прописать history? :-)))))))
Upd: ещё совершенно неясно, как обрабатываются ошибки при чтении и записи файлов. Ну не силён я в питоне... :-(

Это-ж - макет! :)
Я в спецификацию ФБ2 даже не смотрел...
Хых - получите!

<font size="2" face="Consolas, Courier New, Courier, Monospace" color="black"><small><a href="http://s-c.me/9663/s">Copy&nbsp;Source</a>&nbsp;|&nbsp;<a href="http://s-c.me/9663/h">Copy&nbsp;HTML</a></small><ol><li><font color="#696969">#!/usr/bin/python&nbsp;-Ou</font></li>
<li><font color="#696969">#&nbsp;-*-&nbsp;coding:&nbsp;utf-8&nbsp;-*-</font></li>
<li>&nbsp;</li>
<li><font color="#0000ff">import</font> sys, os, getopt, base64, time</li>
<li><font color="#0000ff">import</font> <b>xml</b>.etree.ElementTree</li>
<li><font color="#0000ff">from</font> <b>xml</b>.etree.ElementTree <font color="#0000ff">import</font> ElementTree</li>
<li>&nbsp;</li>
<li>do_extract = False</li>
<li>do_replace = False</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">load</font>(fname,size=None):</li>
<li>&nbsp;&nbsp;fp = None</li>
<li>&nbsp;&nbsp;<font color="#0000ff">try</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;fp = <b>open</b>(fname)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">if</font> size <font color="#0000ff">is</font> None :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = fp.read()</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">else</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;text = fp.read(size)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;fp.close()</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">return</font> text</li>
<li>&nbsp;&nbsp;<font color="#0000ff">except</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">try</font>: fp.close()</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">except</font>: <font color="#0000ff">pass</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> &gt;&gt;<b>sys</b>.stderr,<font color="#008000">'&nbsp;Cannot&nbsp;read'</font>,`fname`,<font color="#008000">'!'</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<b>sys</b>.exit(<font color="#008000">1</font>)</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">save</font>(fname,value):</li>
<li>&nbsp;&nbsp;fp = None</li>
<li>&nbsp;&nbsp;<font color="#0000ff">try</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;fp = <b>open</b>(fname,<font color="#008000">'w'</font>)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;fp.write(value)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;fp.close()</li>
<li>&nbsp;&nbsp;<font color="#0000ff">except</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">try</font>: fp.close()</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">except</font>: <font color="#0000ff">pass</font></li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">get_encoding</font>(fname):</li>
<li>&nbsp;&nbsp;text = <font color="#cc6633">load</font>(fname,<font color="#008000">1024</font>)</li>
<li>&nbsp;&nbsp;i = text.find(<font color="#008000">'&lt;?xml'</font>)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">if</font> i &lt; <font color="#008000">&nbsp;0</font> :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> &gt;&gt;<b>sys</b>.stderr, <font color="#008000">'No&nbsp;valid&nbsp;XML&nbsp;in'</font>, fname</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<b>sys</b>.exit(<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;j = text.find(<font color="#008000">'?&gt;'</font>,i+<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">if</font> i &lt; <font color="#008000">&nbsp;0</font> :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> &gt;&gt;<b>sys</b>.stderr, <font color="#008000">'No&nbsp;valid&nbsp;XML&nbsp;in'</font>, fname</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<b>sys</b>.exit(<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;text = text[i+<font color="#008000">2</font>:j]</li>
<li>&nbsp;&nbsp;i = text.find(<font color="#008000">'encoding='</font>)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">if</font> i &lt; <font color="#008000">&nbsp;0</font> : <font color="#0000ff">return</font> <font color="#008000">'UTF8'</font></li>
<li>&nbsp;&nbsp;text = text[i+<font color="#008000">9</font>:]</li>
<li>&nbsp;&nbsp;<font color="#0000ff">if</font> text <font color="#0000ff">and</font> text[<font color="#008000">&nbsp;0</font>] <font color="#0000ff">in</font> (<font color="#008000">'"'</font>,<font color="#008000">"'"</font>) :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;c,text = text[<font color="#008000">&nbsp;0</font>],text[<font color="#008000">1</font>:]</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;i = text.find(c)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;text = text[:i]</li>
<li>&nbsp;&nbsp;<font color="#0000ff">else</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;text = text.split().pop(<font color="#008000">&nbsp;0</font>)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">return</font> text</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">on_binary</font>(e,level):</li>
<li>&nbsp;&nbsp;indent = <font color="#008000">'./'</font>*level</li>
<li>&nbsp;&nbsp;value = <b>base64</b>.b64decode(e.text)</li>
<li>&nbsp;&nbsp;tag = e.tag.split(<font color="#008000">'}'</font>).pop(-<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'/'</font>+indent,<font color="#008000">'&lt;'</font>+tag+<font color="#008000">'&gt;'</font>,(e.text <font color="#0000ff">is</font> None) <font color="#0000ff">and</font> <font color="#008000">'x'</font> <font color="#0000ff">or</font> <b>len</b>(value),e.attrib</li>
<li>&nbsp;&nbsp;<font color="#0000ff">if</font> do_extract :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#cc6633">save</font>(e.attrib[<font color="#008000">'id'</font>],value)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">elif</font> do_replace :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;value = <font color="#cc6633">load</font>(e.attrib[<font color="#008000">'id'</font>])</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;e.text = <b>base64</b>.b64encode(value)</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">on_image</font>(e,level):</li>
<li>&nbsp;&nbsp;indent = <font color="#008000">'./'</font>*level</li>
<li>&nbsp;&nbsp;tag = e.tag.split(<font color="#008000">'}'</font>).pop(-<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'/'</font>+indent,<font color="#008000">'&lt;'</font>+tag+<font color="#008000">'&gt;'</font>,(e.text <font color="#0000ff">is</font> None) <font color="#0000ff">and</font> <font color="#008000">'x'</font> <font color="#0000ff">or</font> <b>len</b>(e.text),e.attrib</li>
<li>&nbsp;</li>
<li>use_version = None</li>
<li>history_entry = None</li>
<li>version_entry = None</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">on_history</font>(e,level):</li>
<li>&nbsp;&nbsp;<font color="#0000ff">global</font> use_version, history_entry</li>
<li>&nbsp;&nbsp;history_entry = e</li>
<li>&nbsp;&nbsp;indent = <font color="#008000">'./'</font>*level</li>
<li>&nbsp;&nbsp;tag = e.tag.split(<font color="#008000">'}'</font>).pop(-<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'/'</font>+indent,<font color="#008000">'&lt;'</font>+tag+<font color="#008000">'&gt;'</font>,(e.text <font color="#0000ff">is</font> None) <font color="#0000ff">and</font> <font color="#008000">'x'</font> <font color="#0000ff">or</font> <b>len</b>(e.text),e.attrib</li>
<li>&nbsp;&nbsp;<font color="#0000ff">for</font> p <font color="#0000ff">in</font> e :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;tag = p.tag.split(<font color="#008000">'}'</font>).pop(-<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'/'</font>+indent+<font color="#008000">'-&gt;'</font>,<font color="#008000">'&lt;'</font>+tag+<font color="#008000">'&gt;'</font>, <font color="#008000">'='</font>+p.text, p.attrib</li>
<li>&nbsp;&nbsp;<font color="#0000ff">print</font> <b>dir</b>(e)</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">on_version</font>(e,level):</li>
<li>&nbsp;&nbsp;<font color="#0000ff">global</font> use_version, version_entry</li>
<li>&nbsp;&nbsp;version_entry = e</li>
<li>&nbsp;&nbsp;indent = <font color="#008000">'./'</font>*level</li>
<li>&nbsp;&nbsp;tag = e.tag.split(<font color="#008000">'}'</font>).pop(-<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;v = [<b>int</b>(x) <font color="#0000ff">for</font> x <font color="#0000ff">in</font> e.text.split(<font color="#008000">'.'</font>)]</li>
<li>&nbsp;&nbsp;v[-<font color="#008000">1</font>] += <font color="#008000">1</font></li>
<li>&nbsp;&nbsp;use_version = <font color="#008000">'.'</font>.join([<b>str</b>(x) <font color="#0000ff">for</font> x <font color="#0000ff">in</font> v])</li>
<li>&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'/'</font>+indent,<font color="#008000">'&lt;'</font>+tag+<font color="#008000">'&gt;'</font>, <font color="#008000">'='</font>+`e.text`+<font color="#008000">'-&gt;'</font>+use_version,e.attrib</li>
<li>&nbsp;</li>
<li>tag_handler = {</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">'binary'</font>  : <font color="#cc6633">on_binary</font>,</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">'image'</font>   : <font color="#cc6633">on_image</font>,</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">'history'</font> : <font color="#cc6633">on_history</font>,</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">'version'</font> : <font color="#cc6633">on_version</font>,</li>
<li>}</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">traversal</font>(root,level=<font color="#008000">&nbsp;0</font>):</li>
<li>&nbsp;&nbsp;<font color="#0000ff">for</font> e <font color="#0000ff">in</font> root:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;tag = e.tag.split(<font color="#008000">'}'</font>).pop(-<font color="#008000">1</font>)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">if</font> tag_handler.has_key(tag) :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tag_handler[tag](e,level)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#cc6633">traversal</font>(e,level+<font color="#008000">1</font>)</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">handle</font>( fname ):</li>
<li>&nbsp;&nbsp;<font color="#0000ff">global</font> use_version</li>
<li>&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'File'</font>,fname,</li>
<li>&nbsp;&nbsp;fenc = <font color="#cc6633">get_encoding</font>(fname)</li>
<li>&nbsp;&nbsp;s1 = <b>os</b>.path.getsize(fname)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'encoding&nbsp;is'</font>, `fenc`, <font color="#008000">'and&nbsp;the&nbsp;size&nbsp;is'</font>, s1</li>
<li>&nbsp;&nbsp;tree = ElementTree()</li>
<li>&nbsp;&nbsp;root = tree.parse(fname)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">print</font> root.tag,<b>len</b>(root.text)</li>
<li>&nbsp;&nbsp;<font color="#cc6633">traversal</font>(root)</li>
<li>&nbsp;&nbsp;<font color="#0000ff">if</font> do_replace :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#696969">#&nbsp;update&nbsp;history&nbsp;&amp;&nbsp;version,&nbsp;if&nbsp;any</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">if</font> version_entry <font color="#0000ff">is not</font> None :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;version_entry.text = use_version</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'New&nbsp;version:'</font>, use_version</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">else</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'File&nbsp;has&nbsp;no&nbsp;&lt;version&gt;,&nbsp;0.0&nbsp;will&nbsp;be&nbsp;used'</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;use_version = <font color="#008000">'0.0'</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">if</font> history_entry <font color="#0000ff">is not</font> None :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'Adding&nbsp;history&nbsp;entry'</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stamp = <b>time</b>.strftime(<font color="#008000">'%Y-%m-%d&nbsp;%H:%M:%S'</font>)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rec = <font color="#008000">'&lt;p&gt;v&nbsp;'</font>+use_version+<font color="#008000">',&nbsp;'</font>+stamp+<font color="#008000">',&nbsp;binary&nbsp;objects&nbsp;were&nbsp;replaced&nbsp;with&nbsp;fb2img.py&lt;/p&gt;'</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'+'</font>,rec</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p = <b>xml</b>.etree.ElementTree.fromstring(rec)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;history_entry.append(p)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">else</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'File&nbsp;has&nbsp;no&nbsp;&lt;history&gt;'</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#696969">#&nbsp;save&nbsp;the&nbsp;file&nbsp;under&nbsp;.NEW&nbsp;name</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'Writing&nbsp;new&nbsp;file'</font>,fname+<font color="#008000">'.NEW'</font>,<font color="#008000">'...'</font>,</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;tree.write(fname+<font color="#008000">'.NEW'</font>,encoding=fenc)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;s2 = <b>os</b>.path.getsize(fname+<font color="#008000">'.NEW'</font>)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> <font color="#008000">'size&nbsp;was'</font>, s1, <font color="#008000">';&nbsp;new&nbsp;one&nbsp;is'</font>, s2, <font color="#008000">';&nbsp;diff='</font>, s2-s1</li>
<li>&nbsp;</li>
<li><font color="#0000ff">def</font> <font color="#cc6633">main</font>():</li>
<li>&nbsp;&nbsp;<font color="#0000ff">try</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">try</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;opts, args = <b>getopt</b>.<b>getopt</b>(</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>sys</b>.argv[<font color="#008000">1</font>:],</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#008000">'?hxr'</font>,</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;(<font color="#008000">'help'</font>,<font color="#008000">'extract'</font>,<font color="#008000">'replace'</font>)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;)</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">except</font> <b>getopt</b>.error, why:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> &gt;&gt;<b>sys</b>.stderr, <b>sys</b>.argv[<font color="#008000">&nbsp;0</font>],<font color="#008000">':'</font>,why</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">return</font> <font color="#008000">1</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">else</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">for</font> o,v <font color="#0000ff">in</font> opts :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">if</font> o <font color="#0000ff">in</font> (<font color="#008000">'-h'</font>,<font color="#008000">'-?'</font>,<font color="#008000">'--help'</font>):</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> <b>sys</b>.argv[<font color="#008000">&nbsp;0</font>],<font color="#008000">'[-r|--replace&nbsp;|&nbsp;-x|--extract]&nbsp;&lt;fb2-files...&gt;'</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">return</font> <font color="#008000">&nbsp;0</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">elif</font> o <font color="#0000ff">in</font> (<font color="#008000">'-x'</font>,<font color="#008000">'--extract'</font>):</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">global</font> do_extract ; do_extract = True</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">elif</font> o <font color="#0000ff">in</font> (<font color="#008000">'-r'</font>,<font color="#008000">'--replace'</font>):</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">global</font> do_replace ; do_replace = True</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">pass</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">if</font> do_extract <font color="#0000ff">and</font> do_replace :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">print</font> &gt;&gt;<b>sys</b>.stderr,<font color="#008000">'Don`t&nbsp;extract&nbsp;and&nbsp;replace&nbsp;at&nbsp;the&nbsp;same&nbsp;time!'</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">return</font> <font color="#008000">1</font></li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">for</font> arg <font color="#0000ff">in</font> args :</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font color="#cc6633">handle</font>( arg )</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">return</font> <font color="#008000">&nbsp;0</font></li>
<li>&nbsp;&nbsp;<font color="#0000ff">finally</font>:</li>
<li>&nbsp;&nbsp;&nbsp;&nbsp;<font color="#0000ff">pass</font></li>
<li>&nbsp;</li>
<li><font color="#0000ff">if</font> __name__==<font color="#008000">'__main__'</font> :</li>
<li>&nbsp;&nbsp;<b>sys</b>.exit( <font color="#cc6633">main</font>() )</li>
<li><font color="#696969">#&nbsp;vim:ai:sts=2:et</font></li>
<li><font color="#696969">#&nbsp;EOF&nbsp;#&nbsp;</font></li>
</ol></font>

Ну, тут - почти никак ничего не обрабатывается :)

А так: try-except блоки:

<strong>try:</strong>
&nbsp;&nbsp;fp = open('file.xxx')
&nbsp;&nbsp;data = fp.read()
&nbsp;&nbsp;fp.close()
<strong>except</strong> IOError,e:
&nbsp;&nbsp;<strong>print</strong> 'Error:',e

Ну, тут - почти никак ничего не обрабатывается :)

А так: try-except блоки:

.
.
.
.
.
.

<strong>try:</strong>
&nbsp;&nbsp;fp = open('file.xxx')
&nbsp;&nbsp;data = fp.read()
&nbsp;&nbsp;fp.close()
<strong>except</strong> IOError,e:
&nbsp;&nbsp;<strong>print</strong> 'Error:',e

Рыжий Тигра, спасибо за прожку :) Если не трудно, добавьте возможность отключения обновления истории изменения в fb2-файле (description/document-info/history).

Barracuda написал:
возможность отключения обновления истории изменения в fb2-файле (description/document-info/history)
Не-а. Не хочу. Зачем? Надо, чтобы видно было - книга правилась. Особенно если это произошло... э... невзначай. А если кому сильно надо скрыть факт замены картинок - нууу, откроет текстовым редактором и вручную поубирает компромат. :-)
PS. Хинт: исходники доступны. :-)

02/11/2010 - версия 1.5

Аватар пользователя PrePress

Эк вы тут семинар по программированию устроили. Думаете самое место?

PrePress написал:
семинар по программированию устроили. Думаете самое место?
На библиотечном сайте в блоге библиотекаря-программиста? ИМХО таки да! :-)
Аватар пользователя PrePress

Рыжий Тигра написал:
На библиотечном сайте в блоге библиотекаря-программиста? ИМХО таки да! :-)

Я по наивности думал, что записи в публичном блоге должны быть интересны не только двоим-троим. Программа-то ваша не только для программистов. Но кажется ошибался.

PrePress написал:
Программа-то ваша не только для программистов. Но кажется ошибался.
Нууу... вообще-то прога делалась в основном таки для программистов; обсуждение тонкостей же, равно как и взгляд на исходники, поможет многим даже и непрограммистам начать разбираться в программировании - я писал нарочито упрощённым языком, чтобы исходный текст программы был понятен сразу же после прочтения простенькой книжицы по Си и чуть-чуть документации по Mini-XML; а все те, кто желает ни в коем случае не изучить ненароком новый язык и/или новую библиотеку функций, могут попросту не скачивать исходник (http://sourceforge.net/projects/fb2bin/files/ и там файлы fb2bin-1.*.src.7z) и игнорировать соответствующие ветки обсуждения... :-?
PS.
PrePress написал:
Я по наивности думал, что записи в публичном блоге должны быть интересны не только двоим-троим.
Блог пользователя PrePress написал:
PrePress в блог не пишет.
:-)
Аватар пользователя PrePress

Рыжий Тигра написал:
Нууу... вообще-то прога делалась в основном таки для программистов...

А я думал, что для всех работающих с fb2. А ведь пользователи программы с исходниками разбираться и не обязаны вроде бы.

Рыжий Тигра написал:
Блог пользователя PrePress написал:
PrePress в блог не пишет.

:-)

А ничего интересного не придумал.

PrePress написал:
А я думал, что для всех работающих с fb2.
Хм. Пользуешься? Удобно? Есть идеи по улучшению? Излагай, реализую. Не пользуешься - попробуй, не понравилось - "проходи мимо, прохожий, посетитель рынка, собиратель историй" ((L) сапожник Мустафа) - мне тоже есть чем заняться, кроме как генерировать тупые отмазки на ненужные вопросы. :-(

Страницы

X