kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

Visual Studio で kindle 本をビルドする


ネタ的には msbuild と Visual Studio のカスタマイズネタでございます。

Kindle 本のソース

Kindle 本を書く上でのソースは、html と pdf, word 文書等が選べるようです。

細かい事はこっちを参照 Amazon.com: Kindle Direct Publishing: HelpType of Formats

んで、今回は Visual Studio の ASP.NET Web Application の csproj を魔改造して html から mobi 形式で kindle 本をビルドするという形にします。プロジェクトのドキュメントとか、ソリューション内に kindle 本のプロジェクト置けば電子書籍化できるという誰得な物ですね。

というわけで ASP.NET Web Application のプロジェクトを作ります。 MsBuildBook とか付けてますけど、こんな本が Amazon の Kindle ショップに並んでるはずがないよね!

目次を index.html として作ります。

imageimage

この目次はあくまでもコンテンツです、本の中身の目次だと思っても良いでしょう。そういう意味で好き勝手な事を書いて良いです。

目次情報ファイル ncx と opf の作成

とりあえず将来的には T4 とか msbuild カスタムタスクで吐かせるつもりでも一端手書きで書いておきます。

opf も書いておきましょう。

imageimage

ビルドツールの入手

Download KindleGen 2.8 から kindlegen を入手します。プロジェクト内に buildTools フォルダを作り exe を配置しました。

とりあえず opf を叩くとビルドされます。おめでとう。

ビルドされた mobi を kindle の doucments 以下に USB 接続で送り込むと実際に表示を確認する事ができます。

image  IMG_0040IMG_0041

表紙と目次をそれぞれ表示した所、ちゃんと表示できてますね。

目的は自動化だ!(いや、それ手段ですし

さて、手順は解ったという事で自動化していきましょう。

まず、 build そのものをプロジェクトのビルドでできるようにしましょう。

おもむろにプロジェクトをアンロードして 編集に入ります。とりあえず実行してたコマンドを Exec で叩きます。

  <Target Name="MobiBuild" AfterTargets="Build">
    <Exec Command="buildTools\kindlegen MSBuildBook.opf" 
          StdOutEncoding ="utf-8" 
          StdErrEncoding="utf-8"  />
  </Target>

AfterTargets で Build を指定しているので標準の build が動いた後でこのターゲットが実行されますね。

コマンドプロンプトで build したらちゃんと実行結果を得られました。プロジェクトを再読み込みし、リビルドすると、はいできたー。

imageimage

opf 書くのだるいよね

うん、だるいね。本体のHTMLをどんどん書いていけば出来上がるのが理想だね。

csproj には html の一覧あるし、これを元に出したいね。というわけで opf 作成の msbuild のカスタムタスクを書く。

using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace Build.Utilities { public class GenerateOpf : Task { [Required] public ITaskItem Metadata { get; set; } [Required] public ITaskItem[] Contents { get; set; } [Required] public string OutputFile { get; set; } public override bool Execute() { var metadata = XDocument.Load(Metadata.ItemSpec); var doc = new XDocument( new XElement( "package", new XAttribute("unique-identifier", "uid"), CreateMetadataElement(metadata), CreateManifestElement(Contents), new XElement("spine", new XAttribute("toc", "toc")), new XElement("guide") )); doc.Save(OutputFile, SaveOptions.OmitDuplicateNamespaces); return true; } private static XElement CreateManifestElement(IEnumerable<ITaskItem> contents) { var manifestElement = new XElement("manifest"); manifestElement.Add( new XElement( "item", new XAttribute("id", "toc"), new XAttribute("media-type", "application/x-dtbncx+xml"), new XAttribute("href", "toc.ncx"))); foreach (var item in contents.Where( i => Path.GetExtension(i.ItemSpec) == ".html")) { var itemSpec = item.ItemSpec; Debug.Assert(itemSpec != null, "itemSpec != null"); var idValue = Path.GetFileNameWithoutExtension(itemSpec); manifestElement.Add( new XElement( "item", new XAttribute("id", idValue), new XAttribute("media-type", "text/x-oeb1-document"), new XAttribute("href", itemSpec))); } return manifestElement; } private static XElement CreateMetadataElement( XDocument metadata) { return new XElement( "metadata", CreateDcMetadataElement(metadata), CreateXMetadataElement() ); } private static XElement CreateXMetadataElement() { return new XElement( "x-metadata", new XElement( "output", new XAttribute("encoding", "utf-8"), new XAttribute("content-type", "text/x-oeb1-document")), new XElement( "EmbeddedCover", new XText("images/Cover.jpg")) ); } private static XElement CreateDcMetadataElement( XDocument metadata) { XNamespace dc = "http://purl.org/metadata/dublin_core"; XNamespace obe = "http://openebook.org/namespaces/oeb-package/1.0/"; var dcMetadataElement = new XElement( "dc-metadata", new XAttribute(XNamespace.Xmlns + "dc", dc.NamespaceName), new XAttribute(XNamespace.Xmlns + "oebpackage", obe.NamespaceName) ); foreach (var element in metadata.Element("dc-metadata").Elements()) { dcMetadataElement.Add(element); } return dcMetadataElement; } } }

これを csproj に組み込むには以下のように。

  <UsingTask 
    TaskName="GenerateOpf" 
    TaskFactory="CodeTaskFactory" 
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
    <ParameterGroup>
      <Metadata ParameterType="Microsoft.Build.Framework.ITaskItem"
            Required="true" />
      <Contents ParameterType="Microsoft.Build.Framework.ITaskItem[]" 
            Required="true" />
      <OutputFile ParameterType="System.String" Required="true" />
    </ParameterGroup>
    <Task>
      <Reference Include="System.Xml" />
      <Reference Include="System.Xml.Linq" />
      <Code Type="Class" Language="cs"
          Source="..\Build.Utilities\GenerateOpf.cs" />
    </Task>
  </UsingTask>
  <Target Name="OpfGenerate" BeforeTargets="Build">
    <GenerateOpf 
         Metadata="Metadata.xml" 
         Contents="@(Content)" 
         OutputFile="MSBuildBook.opf" />
  </Target>

dublin core なメタデータだけ Metadata.xml に書けば、こんな opf が吐かれる。

<?xml version="1.0" encoding="utf-8"?>
<package unique-identifier="uid">
  <metadata>
    <dc-metadata xmlns:dc="http://purl.org/metadata/dublin_core" xmlns:oebpackage="http://openebook.org/namespaces/oeb-package/1.0/">
      <dc:Title>msbuild をこねる本</dc:Title>
      <dc:Language>en-us</dc:Language>
      <dc:Creator>kazuk</dc:Creator>
      <dc:Description>msbuildをこねてビルドの自動化とかを色々カスタマイズする本です</dc:Description>
      <dc:Date>26/03/2013</dc:Date>
    </dc-metadata>
    <x-metadata>
      <output encoding="utf-8" content-type="text/x-oeb1-document" />
      <EmbeddedCover>images/Cover.jpg</EmbeddedCover>
    </x-metadata>
  </metadata>
  <manifest>
    <item id="toc" media-type="application/x-dtbncx+xml" href="toc.ncx" />
    <item id="index" media-type="text/x-oeb1-document" href="index.html" />
    <item id="MSBuildProjects" media-type="text/x-oeb1-document" href="MSBuildProjects.html" />
    <item id="VSAndMSBuild" media-type="text/x-oeb1-document" href="VSAndMSBuild.html" />
  </manifest>
  <spine toc="toc" />
  <guide />
</package>

html の列挙とかは csproj にある Content のリストからやってるだけだから Web アプリケーションプロジェクトに HTML ページを突っ込んで行けば自動的に opf に組み込まれるので手間がかからない。

ncx も吐いてやろうか!

似たような方法を取れば ncx も吐けます。目次の順序とかは各HTMLからキー要素をなんか抽出してあげるとかすれば良いので手間は似たようなもんでしょう。

まとめ

今日のところはこんな感じ、普通に VS でHTMLを書いてビルドすれば kindle で読める mobi を生成するプロジェクトを作る事ができました。

多分 VS Express でも同じ事はできるはずなんで、無料のExpress でHTMLシコシコ書けば普通に kindle 本を書くのはできそうです。画像とかそういうのを使ったときにどうなるとか、その辺解ったら続編を書くかもしれません。

こんな事はだれもやらねーよかもしれませんけど、VS に markdown から HTML への変換のカスタムツールとか仕込めば、markdown とかでジャカジャカ書いたものを電子書籍にするのも簡単かもしれませんね。

まとめではないこと

自分が本書いて amazon の direct publishing 出したら誰か買いますか?>買うわけないよね!

コメントを残す