ビットマップ画像の情報を表示する

ビットマップ画像の情報(サイズとか)を表示するプログラムを C で書いてみた。

参考ページ

 cf. C言語による画像処理プログラミング
 cf. BMP ファイルフォーマット
 cf. BMPファイルのフォーマット

ファイルヘッダと情報ヘッダ

ビットマップファイルには、ファイルの先頭にファイルヘッダ(BITMAPFILEHEADER)と情報ヘッダが付いているので、これを読み取って表示している。情報ヘッダにはいくつかの種類があるけど、今回のプログラムでは Windows で使われている BITMAPINFOHEADER という形式(長さ 40byte)に決め打ち。手持ちのビットマップファイルの中にはこの BITMAPINFOHEADER (ほかの情報ヘッダとは長さで区別できる)を持つファイルがなかったので、上に挙げた一番初めの参考ページから dog.bmp というファイルを拝借させていただいた。
ちなみに、情報ヘッダには次のような種類があるようだ。

  • BITMAPCOREHEADER (OS/2, 12 bytes)
  • BITMAPINFOHEADER (Windows, 40 bytes)
  • BITMAPV4HEADER (Windows 95/NT4.0, 108 bytes)
  • BITMAPV5HEADER (Windows 98/2000/Me/XP, 124 bytes)

コード

上に書いたように、情報ヘッダは BITMAPINFOHEADER 決め打ち。

#include <stdio.h>
#include <string.h>

typedef struct tagBITMAPFILEHEADER {
    char bfType[3];
    unsigned int bfSize;
    unsigned short bfReserved1;
    unsigned short bfReserved2;
    unsigned long bfOffBits;
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {
    unsigned int   biSize;
    long           biWidth;
    long           biHeight;
    unsigned short biPlanes;
    unsigned short biBitCount;
    unsigned int   biCompression;
    unsigned int   biSizeImage;
    long           biXPixPerMeter;
    long           biYPixPerMeter;
    unsigned long  biClrUsed;
    unsigned long  biClrImportant;
} BITMAPINFOHEADER;

int ReadBMFileHeader(FILE *fp, BITMAPFILEHEADER *header);
int ReadBMInfoHeader(FILE *fp, BITMAPINFOHEADER *header);

int main(int argc, char *argv[])
{
    FILE *fp;
    BITMAPFILEHEADER bmFileHeader = {"", 0, 0, 0, 0};
    BITMAPINFOHEADER bmInfoHeader = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    fp = fopen(argv[1], "rb");
    ReadBMFileHeader(fp, &bmFileHeader);
    ReadBMInfoHeader(fp, &bmInfoHeader);
    fclose(fp);

    printf("File type = %s\n", bmFileHeader.bfType);
    printf("File size = %d bytes\n", bmFileHeader.bfSize);
    printf("Data offset = %ld bytes\n", bmFileHeader.bfOffBits);
    printf("Info header size = %d bytes\n", bmInfoHeader.biSize);
    printf("Width = %ld pixels\n", bmInfoHeader.biWidth);
    printf("Height = %ld pixels\n", bmInfoHeader.biHeight);
    printf("Planes = %d\n", bmInfoHeader.biPlanes);
    printf("Bit count = %d bits/pixel\n", bmInfoHeader.biBitCount);
    printf("Compression = %d\n", bmInfoHeader.biCompression);
    printf("Size image = %d bytes\n", bmInfoHeader.biSizeImage);
    printf("X pixels per meter = %ld\n", bmInfoHeader.biXPixPerMeter);
    printf("Y pixels per meter = %ld\n", bmInfoHeader.biYPixPerMeter);
    printf("Color used = %ld colors\n", bmInfoHeader.biClrUsed);

    return 0;
}

int ReadBMFileHeader(FILE *fp, BITMAPFILEHEADER *header)
{
    char filetype[3] = {'\0', '\0', '\0'};
    unsigned int filesize = 0;
    unsigned char filesize_buf[4];
    unsigned short reserved1, reserved2;
    unsigned long offset = 0;
    unsigned char offset_buf[4];
    int i;

    /* File type */
    fread(&filetype, 1, 2, fp);
    /* File size */
    fread(&filesize_buf, 1, 4, fp);
    for (i = 3; i <= 0; i--) {
        filesize = (filesize << 8) | (unsigned) filesize_buf[i];
    }
    /* Reserved 1 */
    fread(&reserved1, 2, 1, fp);
    /* Reserved 2 */
    fread(&reserved2, 2, 1, fp);
    /* Offset */
    fread(&offset_buf, 1, 4, fp);
    for (i = 3; i <= 0; i--) {
        offset = (offset << 8) | (unsigned long) offset_buf[i];
    }

    strcpy(header->bfType, filetype);
    header->bfSize = filesize;
    header->bfReserved1 = reserved1;
    header->bfReserved2 = reserved2;
    header->bfOffBits = offset;

    return 0;
}

int ReadBMInfoHeader(FILE *fp, BITMAPINFOHEADER *header)
{
    unsigned int   headersize = 0;
    int            width = 0;
    int            height = 0;
    unsigned short planes = 0;
    unsigned short bitcount = 0;
    unsigned int   compression = 0;
    unsigned int   size_image = 0;
    int            x_pix_per_meter = 0;
    int            y_pix_per_meter = 0;
    unsigned int   clr_used = 0;
    unsigned int   clr_important = 0;
    unsigned char buf4[4];
    unsigned char buf2[2];
    int i;

    /* Header size */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        headersize = (headersize << 8) | (unsigned int) buf4[i];
    }
    /* Width */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        width = (width << 8) | (int) buf4[i];
    }
    /* Height */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        height = (height << 8) | (int) buf4[i];
    }
    /* Planes */
    fread(buf2, 1, 2, fp);
    planes = 1;
    /* Bit Count */
    fread(buf2, 1, 2, fp);
    for (i = 1; i >= 0; i--) {
        bitcount = (bitcount << 8) | (unsigned int) buf2[i];
    }
    /* Compression */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
         compression = (compression << 8) | (unsigned int) buf4[i];
    }
    /* Size image */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        size_image = (size_image << 8) | (unsigned int) buf4[i];
    }
    /* X pix per meter */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        x_pix_per_meter = (x_pix_per_meter << 8) | (int) buf4[i];
    }
    /* Y pix per meter */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        y_pix_per_meter = (y_pix_per_meter << 8) | (int) buf4[i];
    }
    /* Color used */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        clr_used = (clr_used << 8) | (unsigned int) buf4[i];
    }
    /* Color important */
    fread(buf4, 1, 4, fp);
    for (i = 3; i >= 0; i--) {
        clr_important = (clr_important << 8) | (unsigned int) buf4[i];
    }

    header->biSize = headersize;
    header->biWidth = width;
    header->biHeight = height;
    header->biPlanes = planes;
    header->biBitCount = bitcount;
    header->biCompression = compression;
    header->biSizeImage = size_image;
    header->biXPixPerMeter = x_pix_per_meter;
    header->biYPixPerMeter = y_pix_per_meter;
    header->biClrUsed = clr_used;
    header->biClrImportant = clr_important;

    return 0;
}

実行例

takatoh@nightschool $ gcc -Wall -o bmpinfo bmpinfo.c
takatoh@nightschool $ ./bmpinfo dog.bmp
File type = BM
File size = 360054 bytes
Data offset = 54 bytes
Info header size = 40 bytes
Width = 400 pixels
Height = 300 pixels
Planes = 1
Bit count = 24 bits/pixel
Compression = 0
Size image = 360000 bytes
X pixels per meter = 1
Y pixels per meter = 1
Color used = 0 colors

clickで–versionオプションを実装する

以前使った Python の click モジュールだけど、コマンドやオプションを定義すると自動で --help オプションを作ってくれる。例えばこんなスクリプトがあったとすると:

# encoding: utf-8

import click

@click.command()
@click.option('--times', '-t', type=int, default=1, help='Repeat.')
@click.option('--morning', '-m', is_flag=True, help='In morning.')
@click.argument('name', default='World')
def main(times, morning, name):
    if morning:
        greeting = 'Good morning'
    else:
        greeting = 'Hello'
    for i in range(times):
        print '{greeting}, {name}!'.format(greeting=greeting, name=name)

if __name__ == '__main__':
    main()

こんな感じでヘルプメッセージを生成してくれる。

takatoh@nightschool $ python hello.py --help
Usage: hello.py [OPTIONS] [NAME]

Options:
  -t, --times INTEGER  Repeat.
  -m, --morning        In morning.
  --help               Show this message and exit.

だけど、--version オプションまでは作ってくれない。まあ、当たり前だ。
で、どうするかというと @click.version_option デコレータを使う。

# encoding: utf-8

import click

script_version = '0.1.0'

@click.command()
@click.option('--times', '-t', type=int, default=1, help='Repeat.')
@click.option('--morning', '-m', is_flag=True, help='In morning.')
@click.version_option(version=script_version)
@click.argument('name', default='World')
def main(times, morning, name):
    if morning:
        greeting = 'Good morning'
    else:
        greeting = 'Hello'
    for i in range(times):
        print '{greeting}, {name}!'.format(greeting=greeting, name=name)

if __name__ == '__main__':
    main()

これで --version オプションが使えるようになる。

takatoh@nightschool $ python hello_version.py --version
hello_version.py, version 0.1.0

ちょっと表示が冗長なので、もう少しシンプルにしてみる。デコレータの引数でメッセージのフォーマットを渡してやる。

# encoding: utf-8

import click

script_version = '0.1.0'

@click.command()
@click.option('--times', '-t', type=int, default=1, help='Repeat.')
@click.option('--morning', '-m', is_flag=True, help='In morning.')
@click.version_option(version=script_version, message='%(prog)s v%(version)s')
@click.argument('name', default='World')
def main(times, morning, name):
    if morning:
        greeting = 'Good morning'
    else:
        greeting = 'Hello'
    for i in range(times):
        print '{greeting}, {name}!'.format(greeting=greeting, name=name)

if __name__ == '__main__':
    main()
takatoh@nightschool $ python hello_version2.py --version
hello_version2.py v0.1.0

ちょっとだけシンプルになった。

参考ページ:
 cf. API – click

pyenvを使って最新のPythonをインストールする

pyenvとは

複数のバージョンの Python を切り替えて使えるようにするもの。Ruby の rvm とか rbenv みたいなもの。

pyenvのインストール

インストールというか、GitHub からクローンしてくる。ここではホームディレクトリ下の .pyenv にクローン。

takatoh@nightschool $ git clone git://github.com/yyuu/pyenv.git .pyenv
Cloning into '.pyenv'...
remote: Counting objects: 11571, done.
remote: Compressing objects: 100% (158/158), done.
remote: Total 11571 (delta 107), reused 0 (delta 0), pack-reused 11391
Receiving objects: 100% (11571/11571), 2.05 MiB | 399.00 KiB/s, done.
Resolving deltas: 100% (8114/8114), done.
Checking connectivity... done.

続いて .bashrc に以下を追記。

#for pyenv
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

シェルを起動し直すと pyenv コマンドが使えるようになる。

takatoh@nightschool $ pyenv
pyenv 20151210-1-ge66dcf2
Usage: pyenv <command> [<args>]

Some useful pyenv commands are:
   commands    List all available pyenv commands
   local       Set or show the local application-specific Python version
   global      Set or show the global Python version
   shell       Set or show the shell-specific Python version
   install     Install a Python version using python-build
   uninstall   Uninstall a specific Python version
   rehash      Rehash pyenv shims (run this after installing executables)
   version     Show the current Python version and its origin
   versions    List all Python versions available to pyenv
   which       Display the full path to an executable
   whence      List all Python versions that contain the given executable

See `pyenv help ' for information on a specific command.
For full documentation, see: https://github.com/yyuu/pyenv#readme

Python 2.7.11をインストール

特定のバージョンをインストールするには pyenv install コマンド。

takatoh@nightschool $ pyenv install 2.7.11
Downloading Python-2.7.11.tgz...
-> https://www.python.org/ftp/python/2.7.11/Python-2.7.11.tgz
Installing Python-2.7.11...
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
Installed Python-2.7.11 to /home/takatoh/.pyenv/versions/2.7.11

あれ、なんか WARNING が出た。インストール自体はできてるようだけど……bzip2 が足りないようだ。このページを参考に bzip2 をインストール。

takatoh@nightschool $ sudo apt-get install -y bzip2
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
bzip2 はすでに最新版です。
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
  ax25-node firefox-locale-en libax25 linux-headers-3.13.0-34
  linux-headers-3.13.0-34-generic linux-image-3.13.0-34-generic
  linux-image-extra-3.13.0-34-generic openbsd-inetd
これを削除するには 'apt-get autoremove' を利用してください。
アップグレード: 0 個、新規インストール: 0 個、削除: 0 個、保留: 21 個。

インストールされてるじゃん。なんで WARNING が出たんだ?
まあいい、このまま進めよう。

Python 3.5.1をインストール

Python3 系の最新バージョンをインストール。

takatoh@nightschool $ pyenv install 3.5.1
Downloading Python-3.5.1.tgz...
-> https://www.python.org/ftp/python/3.5.1/Python-3.5.1.tgz
Installing Python-3.5.1...
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
Installed Python-3.5.1 to /home/takatoh/.pyenv/versions/3.5.1

shimのリフレッシュ

shim がなんだかわからないけど、参考にしたページに書いてあったのでやっておく。

takatoh@nightschool $ pyenv rehash

Pythonのバージョンの切り替え

ここまでの作業で、複数のバージョンがインストールできた。利用可能なバージョンは pyenv versions コマンドで確認できる。

takatoh@nightschool $ pyenv versions
* system (set by /home/takatoh/.pyenv/version)
  2.7.11
  3.5.1
takatoh@nightschool $ python -V
Python 2.7.6

今はシステム標準の 2.7.6。これを 3.5.1 に切り替えるには次のようにする。

takatoh@nightschool $ pyenv global 3.5.1
takatoh@nightschool $ pyenv versions
  system
  2.7.11
* 3.5.1 (set by /home/takatoh/.pyenv/version)

バージョンの切り替えには、もうひとつ pyenv local コマンドもある。どう違うのかよくわからない。

ともかく、これでバージョンを切り替えて使えるようになった。

[追記]

参考ページ
 cf. Macでpyenv+virtualenv – Qiita

カレントディレクトリ直下のディレクトリ一覧を得る

ls コマンドのオプションにディレクトリだけ表示するのがあればよかったのに、ないらしい。なので awk と組み合わせた。

takatoh@nightschool $ ls -l . | awk '/^d/ { print $9 }'
Desktop
Documents
Downloads
Dropbox
Music
Pictures
Public
Templates
Videos
bin
lib
tmp
w

文字列間のレーベンシュタイン距離を求める(2)Haskell版

一昨日のお題を、Haskell でやってみた。

module Main where

import System.Environment (getArgs)

mValue :: [[a]] -> Int -> Int -> a
mValue m i j = (m !! i) !! j

mReplace :: [[a]] -> Int -> Int -> a -> [[a]]
mReplace m i j v = bi ++ [bj ++ [v] ++ aj] ++ ai
  where
    bi = take i m
    ji = m !! i
    ai = drop (i + 1) m
    bj = take j ji
    aj = drop (j + 1) ji

ldInit :: String -> String -> [[Int]]
ldInit s1 s2 = [[0 | j <- [0..n]] | i <- [0..m]]
  where
    m = length s1
    n = length s2

ldBuild :: ([[Int]], String, String) -> (Int, Int) -> ([[Int]], String, String)
ldBuild (m, s1, s2) (i, j) = (mReplace m i j v, s1, s2)
  where
    v = ldCalc m s1 s2 i j

ldCalc :: [[Int]] -> String -> String -> Int -> Int -> Int
ldCalc m s1 s2 i j =
  if i == 0 && j == 0 then
    0
  else if i == 0 then
    j
  else if j == 0 then
    i
  else
    let x = if (s1 !! (i-1)) == (s2 !! (j-1)) then 0 else 1
  in
  minimum [(mValue m i (j-1)) + 1, (mValue m (i-1) j) + 1, (mValue m (i-1) (j-1)) + x]

levenshteinDistance :: String -> String -> Int
levenshteinDistance s1 s2 = mValue ldM (length s1) (length s2)
  where
    m = ldInit s1 s2
    ij = [(i, j) | i <- [0..(length s1)], j <- [0..(length s2)]]
    (ldM, _, _) = foldl ldBuild (m, s1, s2) ij

main :: IO ()
main = do
  args <- getArgs
  let s1 = args !! 0
  let s2 = args !! 1
  print $ levenshteinDistance s1 s2

0 で初期化した二次元リスト(マトリックス)から foldl を使ってレーベンシュタイン距離のマトリックスを作る、というアイデアは出たんだけど、マトリックスを 0 で初期化する(ldInit)のと要素を置き換えたマトリックスを作る(mReplace)のがなかなか難産だった。結果として ldInit はリスト内包表記を使って簡潔にかけたけど、mReplace のほうはなんか泥臭くなってしまった。もっとエレガントにいかないもんだろうか。 実行例:

takatoh@nightschool $ runhaskell ld.hs apple play
4
takatoh@nightschool $ runhaskell ld.hs perl pearl
1

Pythonでは空文字列は偽

空文字列は偽で、それ以外の文字列は真と評価される。忘れてたよ。
せっかくなので確認しておこう。

  • 空文字列(””)は偽、それ以外は真
  • 数値の 0 は偽、それ以外は真
  • 空の配列は偽、それ以外は真
  • 空の辞書は偽、それ以外は真
  • 空のタプルは偽、それ以外は真

これ以外では、True は真、False と None は偽。

確認してみよう。

>>> def true_or_false(obj):
...     if obj:
...         return True
...     else:
...         return False
... 
>>> true_or_false("")
False
>>> true_or_false("a")
True
>>> true_or_false(0)
False
>>> true_or_false(-1)
True
>>> true_or_false([])
False
>>> true_or_false([1])
True
>>> true_or_false({})
False
>>> true_or_false({'a':'andy'})
True
>>> true_or_false(())
False
>>> true_or_false((1,))
True

文字列間のレーベンシュタイン距離を求める

レーベンシュタイン距離というのを知った。

Wikipedia のレーベンシュタイン距離の項によると:

レーベンシュタイン距離(レーベンシュタインきょり)は、二つの文字列がどの程度異なっているかを示す距離(距離空間を参照)である編集距離(へんしゅうきょり)の一種類である。具体的には、文字の挿入や削除、置換によって、一つの文字列を別の文字列に変形するのに必要な手順の最小回数として与えられる。名称は、1965年にこれを考案したロシアの学者ウラジミール・レーベンシュタインにちなむ。

だそう。上記ページに擬似コードによる実装例が載ってるのと、次のページの Python での実装例が参考になった。なので JavaScript でやってみた。

 cf. 編集距離 (Levenshtein Distance) – naoyaのはてなダイアリー

#!/usr/bin/env node

function levenshtein_distance(a, b) {
  var m = [];
  var i, j, x;

  for (i = 0; i <= a.length; i++) {
    m[i] = [];
  }
  for (i = 0; i <= a.length; i++) {
    m[i][0] = i;
  }
  for (j = 0; j <= b.length; j++) {
    m[0][j] = j;
  }
  for (i = 1; i <= a.length; i++) {
    for (j = 1; j <= b.length; j++) {
      if (a[i-1] == b[j-1]) {
        x = 0;
      } else {
        x = 1;
      }
      m[i][j] = Math.min(m[i-1][j] + 1, m[i][j-1] + 1, m[i-1][j-1] + x);
    }
  }
  for (i = 0; i <= a.length; i++) {
    console.log(m[i]);
  }

  return m[a.length][b.length];
}

var s1 = process.argv[2];
var s2 = process.argv[3];
console.log(levenshtein_distance(s1, s2));

実行例:

takatoh@nightschool $ ./ld.js apple play
4
takatoh@nightschool $ ./ld.js perl pearl
1

Pythonの辞書から値がNoneであるキーを削除する

Ruby では Hash#reject を使えば簡単にできるのに、Python には使えそうなメソッドが見当たらない。

>>> def delete_if_none(dic):
...     for k, v in dic.items():
...         if v is None:
...             del(dic[k])
...     return dic
... 
>>> dic
{'a': 'Andy', 'c': 'Charlie', 'b': None}
>>> dic2 = delete_if_none(dic)
>>> dic2
{'a': 'Andy', 'c': 'Charlie'}
>>> dic
{'a': 'Andy', 'c': 'Charlie'}

JSONをPOSTする

昨日のエントリではクライアントを Ruby で書いたけども、サーバ側が Python(Flask) なのでやっぱりクライアントも Python がいい。そういうわけで JSON を POST するのにいいライブラリがないかとググってみたら、requests というのが良さそうだ。

 cf. Requests: 人間のためのHTTP

で、書いてみたのがこれ:

#!/usr/bin/env python
# encoding: utf-8

import sys
import click
import yaml
import csv
import requests

def post_book(data, uri_base):
    post_data = {
        'title' : data['title'],
        'volume' : data['volume'] or '',
        'series' : data['series'] or '',
        'series_volume' : data['series_volume'] or '',
        'author' : data['author'] or '',
        'translator' : data['translator'] or '',
        'publisher' : data['publisher'] or '',
        'category' : data['category'] or 'その他',
        'format' : data['format'] or 'その他',
        'isbn' : data['isbn'] or '',
        'published_on' : data['published_on'] or '',
        'original_title' : data['original_title'] or '',
        'note' : data['note'] or '',
        'keyword' : data['keyword'] or '',
        'disk' : data['disk'] or '',
        'disposed' : data['disposed'] or '0'
    }
    uri = uri_base + 'api/book/add/'
    res = requests.post(uri, json=post_data)
    print title_with_vol(res.json()['books'][0])

def title_with_vol(book):
    if book['volume'] == '':
        return book['title']
    else:
        return book['title'] + ' [' + book['volume'] + ']'

def load_yaml(yamlfile):
    f = open(yamlfile, 'r')
    data = yaml.load(f)
    f.close()
    return data['books']

def load_csv(csvfile):
    f = open(csvfile, 'r')
    reader = csv.DictReader(f)
    data = []
    for row in reader:
        data.append(row)
    f.close()
    return data

@click.group()
@click.pass_context
@click.option('--repository', '-R', help='Specify repository.')
def cmd(ctx, repository):
    if repository is None:
        raise click.BadParameter('--repository option is required.')
    ctx.obj['repository'] = repository.rstrip('/') + '/'

@cmd.command(help='Post books to Bruschetta.')
@click.pass_context
@click.option('--csv', is_flag=True, help='Input from CSV.')
@click.argument('input')
def post(ctx, csv, input):
    if csv:
        books = load_csv(input)
    else:
        books = yaml_load(input)
    for book in books:
        post_book(book, ctx.obj['repository'])

def main():
    cmd(obj={})


main()

ついでに、サブコマンドとオプションの処理には click というライブラリを使った。click についてはココらへんが詳しい。

 cf. Python: コマンドラインパーサの Click が便利すぎた – CUBE SUGAR CONTAINER
 cf. click

実行例:

takatoh@nightschool $ python bin/post_book.py --repository http://localhost:5000/ post --csv books.csv
ウルフ・ウォーズ
世界の涯ての夏
冴えない彼女の育てかた [9]

うまくいった。

FlaskでJSONを受け取る

Flask で POST された JSON を受け取るには request.json を使えばいい。
サーバー側(Flask)のコードはこんな感じ:

from flask import request, redirect, url_for, render_template, flash, json

(中略)

@app.route('/api/book/add/', methods=['POST'])
def api_book_add():
    categ = Category.query.filter_by(name=request.json['category']).first()
    fmt = Format.query.filter_by(name=request.json['format']).first()
    book = Book(
        title = request.json['title'],
        volume = request.json['volume'],
        series = request.json['series'],
        series_volume = request.json['series_volume'],
        author = request.json['author'],
        translator = request.json['translator'],
        publisher = request.json['publisher'],
        category_id = categ.id,
        format_id = fmt.id,
        isbn = request.json['isbn'],
        published_on = request.json['published_on'],
        original_title = request.json['original_title'],
        note = request.json['note'],
        keyword = request.json['keyword'],
        disk = request.json['disk']
    )
    if request.json['disposed'] == '1':
        book.disposed = True
    db.session.add(book)
    db.session.commit()
    return json.dumps({ "status": "OK", "books": [book.to_dictionary()]})

POST するクライアント側(こっちは Ruby)はこう:

#!/usr/bin/env ruby
# encoding: utf-8

require 'httpclient'
require 'yaml'
require 'csv'
require 'json'
require 'optparse'

def post_book(book)
  post_url = "http://localhost:5000/api/book/add/"
  post_data = {
    "title"          => book["title"],
    "volume"         => book["volume"]         || "",
    "series"         => book["series"]         || "",
    "series_volume"  => book["series_volume"]  || "",
    "author"         => book["author"]         || "",
    "translator"     => book["translator"]     || "",
    "publisher"      => book["publisher"]      || "",
    "category"       => book["category"]       || "その他",
    "format"         => book["format"]         || "その他",
    "isbn"           => book["isbn"]           || "",
    "published_on"   => book["published_on"]   || "",
    "original_title" => book["original_title"] || "",
    "note"           => book["note"]           || "",
    "keyword"        => book["keyword"]        || "",
    "disk"           => book["disk"]           || "",
    "disposed"       => book["disposed"]       || "0"
  }
  json_data = post_data.to_json

  res = @client.post_content(post_url, json_data, "Content-Type" =&gt; "application/json")
  result = JSON.parse(res)
  puts title_with_vol(result["books"].first)
end

def title_with_vol(book)
  if book["volume"].nil? || book["volume"].empty?
    book["title"]
  else
    book["title"] + " [" + book["volume"] + "]"
  end
end

options = {}
opts = OptionParser.new
opts.banner = <<EOB

Options:
EOB
opts.on("--csv", "Input from CSV."){|v| options[:csv] = true }
opts.on_tail("--help", "-h", "Show this message"){|v|
  puts opts.help
  exit(0)
}
opts.parse!

@client = HTTPClient.new

inputfile = ARGV.shift
books = if options[:csv]
  csvfile = File.open(inputfile, "r")
  CSV.new(csvfile, headers: true)
else
  YAML.load_file(inputfile)["books"]
end
books.each do |book|
  post_book(book)
end

HTTPClient#post_content に JSON と "Content-Type" => "application/json" を渡してるのがミソ。Content-Typeapplication/json になってることで、Flask.request.json でデコードされたデータが得られる。