C言語:ファイルをいじる

ファイルの重複チェック
ファイルの中身を逆にする
csvファイルを愛でる
ファイルを分割
ファイルを結合
連長圧縮でもしてみる
6chwavを2chにダウンミックスしてみる
Thumbs.dbからjpegファイルを抽出する

ファイルの重複チェック

ファイルの重複チェックです。一応DOS窓にはFCコマンドがありますが。

コードです。ファイルサイズを比較して、同じなら内容を比較します。それだけです。

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

/* f1とf2を比較。-1,-2ファイル開けない0=内容違う1=同じ */
int fc(char *f1, char *f2)
{
    FILE *fp[2];
    char *buf[2];
    long siz[2];
    int i, ret = 0;
    
    if ((fp[0] = fopen(f1, "rb")) == NULL) return -1;
    if ((fp[1] = fopen(f2, "rb")) == NULL) {
        fclose(fp[0]);
        return -2;
    }
    
    /* ファイルサイズ調べる */
    for (i = 0; i < 2; i++){
        fseek(fp[i], 0, SEEK_END);
        siz[i] = ftell(fp[i]);
        rewind(fp[i]);
    }
    if (siz[0] != siz[1]) goto owari;
    
    /* ファイルの内容を比較する */
    for (i = 0; i < 2; i++) {
        buf[i] = malloc(siz[0]);
        fread(buf[i], 1, siz[0], fp[i]);
    }
    if (!memcmp(buf[0], buf[1], siz[0])) ret = 1;
    for (i=0; i < 2; i++) free(buf[i]);
    
owari:
    for (i=0; i < 2; i++) fclose(fp[i]);
    return ret;
}

/* エラーメッセージ出して終わり */
void errmsg(char *mes)
{
    printf("エラーです。%s\n", mes);
    exit(0);
}

main(int argc, char **argv)
{
    int r;
    
    if (argc < 3) errmsg("引数不足");
    if ((r = fc(argv[1], argv[2])) < 0) errmsg("ファイル開けない");
    printf("[%s]と[%s]は%s\n", argv[1], argv[2], r ? "同じ" : "違う" );
    return 0;
}

ファイルの中身を逆にする

何の意味があるのか分かりませんが。ファイルを隠すとき位しか用途はなさそうですが。 大きなファイルの場合、時間がかかると思います。

コードです。"ファイルをいじる"というよりは"ポインタをいじる"ような感じですけど。

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

/* ファイルをリバースして上書き 1=成功 0=失敗 */
int fr(char *filename)
{
    FILE *fp;
    long siz;
    char *buf, *atama, *oshiri;
    char tmp;
    
    fp = fopen(filename, "r+b");
    if(fp == NULL) return 0;
    
    /* bufにファイル入れる */
    fseek(fp, 0, SEEK_END);
    siz = ftell(fp);
    rewind(fp);
    atama = buf = malloc(siz);
    oshiri = buf + siz -1;
    fread(buf, 1, siz, fp);
    rewind(fp);
    
    /* リバース */
    do {
        tmp = *atama;
        *atama = *oshiri;
        *oshiri = tmp;
    } while (++atama < --oshiri);
    
    /* 書き込んで終わり */
    fwrite(buf, 1, siz, fp);
    free(buf);
    fclose(fp);
    return 1;
}

int main(int argc , char **argv)
{
    if (argc  > 1)
        while (--argc > 0)
            printf("[%s]リバースは%s\n", argv[argc], fr(argv[argc]) ? "成功" : "失敗");
    return 0;
}

csvファイルを愛でる

最近話題のcsvファイルを読み込んで、htmlのtableに変換する画期的なプログラムです。 ファイルをfgetsで1行ずつ読み込んでstrtokで区切っています。 でもstrtokの仕様で「,,」のような空のデータは無視されます。

/**
 * csv -> table sky_tblerer
 * 注意:空(,,,こんなの)は無視されるので注意
 ******************************/

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

#define err(x) {printf("%s\n", (x));return;}

main(int argc, char* argv[])
{
    FILE *fp;
    char buf[200];
    char *p;
    
    if (argc < 2) err("引数不足。csvファイルを指定してくださいね♪");
    
    if ((fp = fopen(argv[1], "r") == NULL) err("ファイルを読み込めないんですが");
    
    printf("<table border=1>\n");
    /* 1行ずつ読み込む */
    while (fgets(buf, 200, fp) != NULL) {
        /* コンマ、改行で分割 */
        p = strtok(buf, ",\n");
        printf("<tr>");
        while (p != NULL) {
            printf("<td>%s</td>", p);
            p = strtok(NULL, ",\n");
        }
        printf("</tr>\n");/* ここで改行 */
    }
    printf("</table>");
    fclose(fp);
    
    return ;
}

ファイルを分割

今回はファイル分割です。結合バッチファイルも作ってしまいます。だから、DOS窓専用です。

EXTは分割ファイルの拡張子です。.kumaにしています。 usage関数はソースファイル名を好きな使えるように__FILE__マクロを使っています。 でもやめたほうがいいかもしれません。

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

#define EXT ".kuma"
#define BATFILE "ketugou.bat"

/* 引数足りんときの終了 */
void usage(void)
{
    char file[100];
    char *p;
    
    /* ファイル名を作る */
    strcpy(file, __FILE__);
    p = strrchr(file, '.');
    if (p != NULL) *p = 0;
    
    printf("ファイル分割ソフトです。\n"
         "%s 分割するファイル 分割サイズ\n", 
         file);
    exit(0);
}

/* エラー終了 */
void era_desu(char *kome)
{
    printf("エラーです。%s", kome);
    exit(1);
}

/* 結合バッチを作る */
void Ketugou(char *fname, int kaunto)
{
    FILE *fp;
    int c;
    
    fp = fopen(BATFILE, "w");
    if (fp == NULL) era_desu("結合バッチ作れん");
    fprintf(fp, "copy /b ");
    for (c = 0; c < kaunto-1; c++) fprintf(fp ,"\"%s_%d%s\" + ",fname, c, EXT);
    fprintf(fp ,"\"%s_%d%s\" \"%s\"\n",fname, kaunto-1, EXT, fname);
    fprintf(fp ,"del *%s %s\n", EXT, BATFILE);/* 分割ファイルと自分自身を削除 */
    fclose(fp);
    puts("結合バッチを作りました。");
}

/* メーン関数 */
main(int argc, char **argv)
{
    char fname[100];
    char outfile[100];
    unsigned size;
    char *buf;
    FILE *ifp, *ofp;
    int kaunto = 0;
    size_t read;
    
    if (argc < 3) usage();/* 引数不足*/
    
    strcpy(fname, argv[1]);
    ifp = fopen(fname, "rb");
    if (ifp == NULL) era_desu("ファイル開けん。");
    
    size = atoi(argv[2]);
    if (size < 1) era_desu("分割サイズが変です。");
    
    buf = (char *)malloc(size);
    if (buf == NULL) era_desu("メモリ無いよ。");
    
    printf("[%s]を[%d]バイトで分割します。\n", fname, size);/* 情報開示 */
    
    /* 入力がなくなるまで頑張る */
    while ((read = fread(buf, 1, size, ifp))) {
        sprintf(outfile, "%s_%d%s",fname, kaunto, EXT);/* 出力ファイル名生成 */
        kaunto++;
        
        /* 書き込む */
        ofp = fopen(outfile, "wb");
        if (ofp == NULL) era_desu("出力ファイル開けん。");
        if (fwrite(buf, 1, read, ofp) == 0) era_desu("書き込みエラー。");;
        fclose(ofp);
        
        printf("[%s]を作りました。(%dバイト)\n", outfile, read);//情報開示
    }
    fclose(ifp);
    Ketugou(fname, kaunto);//結合バッチファイルを作る。
    puts("終わり");
}

ファイルを結合

ファイルの分割が出来たらファイルの結合もしたいのが人情。 ということで結合ソフトを作りました。

使い方は引数に、結合後ファイル名 結合するファイル........ を入れます。

#include <stdio.h>

#define bufsiz 4096

/* 引数不足 */
void usage(void)
{
    puts("ファイル結合ソフトです。\n"
        "引数について\n"
        "1個目:結合後ファイル名\n"
        "2個目から結合するファイル(順番に注意)");
    
    exit(0);
}

main(int argc, char *argv[])
{
    FILE *infp, *outfp;
    char buf[bufsiz];
    int p;
    int readsize;
    
    if (argc < 3) usage();
    
    outfp = fopen(argv[1], "wb");
    if (outfp == NULL) {
        puts("出力ファイル開けん。終わり");
        return -1;
    }
    
    printf("ファイル[%s]結合開始!\n", argv[1]);
    
    for (p=2; p < argc; p++) {
        infp  = fopen(argv[p], "rb");
        if (outfp == NULL) {
            puts("入力ファイル開けん。終わり");
            return -2;
        }
        
        printf("%s →%s\n", argv[p], argv[1]);
        while ((readsize = fread(buf, 1, bufsiz, infp)) != 0)
            fwrite(buf, 1, readsize, outfp);
            
        fclose(infp);
    }
    fclose(outfp);
    
    return ;
}

連長圧縮でもしてみる

さて、ろくなプログラムを作れない私ですが、無謀にも連長圧縮に挑戦してみました。

ランレングス圧縮とも言うそうです。AAABBCCCCDEをA3B2C4D1E1というようにするらしいです。 自分自身のコードを連長圧縮したところ、160%になってしまいました。 main関数のエンコードとデコードの分岐がムダなような気もします。

使い方は[入力ファイル 出力ファイル]でエンコード、 [入力ファイル 出力ファイル -d]でデコードです。

/*連長圧縮 ver0.2 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* バッファサイズ。絶対に偶数。 */
#define PSIZ 4096

#define ENCODE 0
#define DECODE 1

typedef struct {
    unsigned char *sbuf; /* 元のデータ */
    int ssiz; /*sbufのサイズ */
    unsigned char *dbuf; /*圧縮データ */
    int dsiz; /*dbufのサイズ */
} RLS1;

/* エンコード関数 */
int rlenc1(RLS1 *rl)
{
    unsigned char cnt, c;
    int n, m;
    
    cnt = n = m = 0;
    while (n < rl->ssiz) {
        c = rl->sbuf[n];
        cnt = 0;
        while (c == rl->sbuf[n]) {
            cnt++;
            n++;
            if ((n >= rl->ssiz) || (cnt >= 255)) break;
        }
        rl->dbuf[m] = c;
        rl->dbuf[m+1] = cnt;
        m +=2;
    }
    rl->dsiz = m;

    return rl->dsiz;
}

/* デコード関数 */
int rldnc1(RLS1 *rl)
{
    int n;
    
    rl->dsiz = 0;
    for (n=0; n < rl->ssiz ;n+=2 ) {
        memset(&rl->dbuf[rl->dsiz], rl->sbuf[n], rl->sbuf[n+1]);
        rl->dsiz += rl->sbuf[n+1];
    }
    return rl->dsiz;
}

/* RLS1構造体前処理 (メモリ確保するだけ) */
int rl1_syokika(RLS1 *rl, int siz, int mode)
{
    rl->sbuf = malloc(siz);
    if (rl->sbuf == NULL) return 0;
    if (mode == ENCODE) {
           rl->dbuf = malloc(siz*2);/* 最大で2倍になる */
    } else {
        rl->dbuf = malloc(siz*255);/* 最大で255倍になる */
    }
    if (rl->dbuf == NULL) return 0;
    return 1;
}

/* RLS1構造体後処理(メモリ開放するだけ) */
int rl1_owari(RLS1 *rl)
{
    free(rl->sbuf);
    free(rl->dbuf);
    return 1;
}

int main(int argc, char **argv)
{
    FILE *ifp, *ofp;
    RLS1 rl;
    int sall, dall;
    
    if (argc < 3) {
        puts("引数の与え方は 入力ファイル 出力ファイル [-d] です。\n");
        return 1;
    }
    ifp = fopen(argv[1], "rb");
    if (ifp == NULL) {
        puts("エラー!:入力ファイル開けん!");
        return 2;
    }
    ofp = fopen(argv[2], "wb");
    if (ofp == NULL) {
        puts("エラー!:出力ファイル開けん!");
        fclose(ifp);
        return 3;
    }
    sall = dall = 0;/* 初期化 */
    if (argc >= 4 && !memcmp(argv[3], "-d", 2)) {
        /* デコード */
        rl1_syokika(&rl, PSIZ, DECODE);
        printf("復元開始\n%s -> %s\n", argv[1], argv[2]);
        while ((rl.ssiz = fread(rl.sbuf, 1, PSIZ, ifp)) > 0) {
            rldnc1(&rl);
            fwrite(rl.dbuf, 1, rl.dsiz, ofp);
            printf("%d -> %d (%d%%)\n", rl.ssiz, rl.dsiz , rl.dsiz * 100 / rl.ssiz);
            sall += rl.ssiz;
            dall += rl.dsiz; 
        }
        printf("-------------------------------------------\n"
        "%d -> %d (%d%%)\n", sall, dall , dall * 100 / sall);
        puts("復元完了!");
    } else {
        /* エンコード */
        rl1_syokika(&rl, PSIZ, ENCODE);
        printf("圧縮開始\n%s -> %s\n", argv[1], argv[2]);
        while ((rl.ssiz = fread(rl.sbuf, 1, PSIZ, ifp)) > 0) {
            rlenc1(&rl);
            fwrite(rl.dbuf, 1, rl.dsiz, ofp);
            printf("%d -> %d (%d%%)\n", rl.ssiz, rl.dsiz , rl.dsiz * 100 / rl.ssiz);
            sall += rl.ssiz;
            dall += rl.dsiz; 
        }
        printf("-------------------------------------------\n"
        "%d -> %d (%d%%)\n", sall, dall , dall * 100 / sall);
        puts("圧縮完了!");
    }
    
    /* 後始末 */
    rl1_owari(&rl);
    fclose(ifp);
    fclose(ofp);
    return 0;
}

6chwavを2chにダウンミックスしてみる

AC3、DTSなど5.1chサラウンドがもてはやされた時代がありましたが、昨今ではあまり人気がありませんよね。

ここではすでに5.1ch(6ch)のwavファイルにデコードされたものを2chにダウンミックスする方法を考えます。

6chの構造はFL,FR,C,LFE,SL,SRの順に並んでいるそうです。 それ以外は普通のwavファイルと同じらしい。

次のプログラムは6chwavを2chに変換します。 引数は1つめが入力ファイル名、2つめが出力ファイル名(省略可)です。

ファイルサイズは出力サイズが4GBつまり12GBの入力までなら多分大丈夫だとおもいます。

//6ch->2ch ダウンミックス

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

#define SAMPLECOUNT 48000
#define DEFAULT_OUTFILENAME "鈴木えみちゃんは天使.wav"
#define MEMSIZE (SAMPLECOUNT * sizeof(short) * 12)

//6ch配置対応表
enum {
    FL = 0,
    FR,
    C,
    LFE,
    SL,
    SR,
};

enum {
    DM_NOERROR = 0,
    DM_ARG_ERROR,
    DM_INIT_ERROR,
    DM_WAVHDR_ERROR,
    DM_END,
};

#pragma pack(push,1)
//wavファイルのヘッダ
typedef struct{
    char RIFF[4];//"RIFF"
    DWORD bytes;//(ファイルのバイト数)-8
    char WAVE[4];// "WAVE" 
    char fmt[4];// "fmt "
    DWORD siz_wf;//PCMWAVEFORMAT構造体のバイト数=常に16
    //PCMWAVEFORMAT構造
    WORD wFormatTag; //普通は1
    WORD nChannels; //チャンネル数
    DWORD nSamplesPerSec; //サンプリングレート
    DWORD nAvgBytesPerSec; //1秒間あたりのバイト数
    WORD nBlockAlign;  //1サンプルに必要なバイト数 ch * bits/8
    WORD wBitsPerSample;//量子化ビット数
    char data[4];//"data"
    DWORD pcmbytes;//波形データのバイト数
} WAVHEADER;
#pragma pack(pop)

//ダウンミックスに使う変数
typedef struct {
    int smpl;//サンプル数
    short *in;//入力バッファ
    short *out;//出力バッファ
    FILE *ifp;//入力ファイル
    FILE *ofp;//出力ファイル
    WAVHEADER wh;//WAVHEADER構造体
} DownMixArg;

//プロトタイプ

//公開可能
int init_downmix(DownMixArg *pdma, char *infile, char *outfile, int smpl);
int downmix62(DownMixArg *pdma);
int end_downmix(DownMixArg *pdma);

//下請け
void free_DownMixArg(DownMixArg *pdma);
short clip(int i);
void err_exit(char *mes, int code);
void defaultname(char *buf);



//初期化
//入力ファイル名infile、出力ファイル名outfile、サンプル数smplを
//使って、pdmaを初期化する。
int init_downmix(DownMixArg *pdma, char *infile, char *outfile, int smpl)
{
    char outfile2[333];
    if (pdma == NULL || infile == NULL || smpl == 0) return DM_ARG_ERROR;//引数がおかしい
    
    if (outfile == NULL) {
        defaultname(outfile2);
        outfile = outfile2;
    }

    pdma->in = pdma->out = NULL;
    pdma->ifp = pdma->ofp = NULL;
    
    //初期化
    pdma->ifp = fopen(infile, "rb");
    pdma->ofp = fopen(outfile, "wb");
    pdma->smpl = smpl;
    pdma->in = malloc(sizeof(short) * 6 * pdma->smpl);
    pdma->out = malloc(sizeof(short) * 2 * pdma->smpl);
    
    if (pdma->ifp == NULL || pdma->ofp == NULL || pdma->in == NULL || pdma->out == NULL) {
        free_DownMixArg(pdma);
        return DM_INIT_ERROR;
    }
    
    //続いて初期設定
    fread(&pdma->wh, sizeof(WAVHEADER), 1, pdma->ifp);//ヘッダ読み込み
    if (pdma->wh.nChannels != 6 || pdma->wh.wBitsPerSample != 16 || 
            memcmp(pdma->wh.data, "data", 4) != 0) {
        free_DownMixArg(pdma);
        return DM_WAVHDR_ERROR;
    }
    
    pdma->wh.wFormatTag = WAVE_FORMAT_PCM;//=1
    pdma->wh.nChannels = 2;//2ch
    pdma->wh.nBlockAlign = pdma->wh.nChannels * pdma->wh.wBitsPerSample/8;
    pdma->wh.nAvgBytesPerSec = pdma->wh.nSamplesPerSec * pdma->wh.nBlockAlign;
    
    //とりあえず仮のサイズにしてヘッダを書き込む
    pdma->wh.pcmbytes= pdma->wh.nAvgBytesPerSec * 300;//300秒
    pdma->wh.bytes = pdma->wh.pcmbytes + sizeof(WAVHEADER) - 8;
    fwrite(&pdma->wh, sizeof(WAVHEADER), 1, pdma->ofp);
    
    pdma->wh.pcmbytes = 0;//
    
    return DM_NOERROR;
}

//DownMixArg構造体を使ってダウンミックスを行う
//DM_NOERRORが戻れば繰り返す。それ以外はend_downmixを呼ぶ。
int downmix62(DownMixArg *pdma)
{
    int ret, j;
    short *pout = pdma->out;
    short *pin = pdma->in;    
    
    if (pdma == NULL) return DM_ARG_ERROR;//引数がおかしい
    if (pdma->ifp == NULL || pdma->ofp == NULL || pdma->in == NULL || pdma->out == NULL) {
        return DM_ARG_ERROR;//引数がおかしい
    }
    
    //読み込む
    ret = fread(pdma->in, 2, 6 * pdma->smpl, pdma->ifp);//
    if (ret < 6) return DM_END;//読み込むものがない。

    //ダウンミックス
    j = 0;
    while (ret >= 6) {
        *pout++ = clip(pin[FL] + pin[C] + pin[SL]);
        *pout++ = clip(pin[FR] + pin[C] + pin[SR]);
        j += 2;
        ret -= 6;
        pin += 6;
    }
    
    //書き込む
    ret = fwrite(pdma->out, sizeof(short), j, pdma->ofp);
    pdma->wh.pcmbytes += (ret * sizeof(short));
    
    return DM_NOERROR;
}

//終了およびメモリ開放
int end_downmix(DownMixArg *pdma)
{
    if (pdma == NULL) return DM_ARG_ERROR;//引数がおかしい
    if (pdma->ifp == NULL || pdma->ofp == NULL) {
        free_DownMixArg(pdma);
        return DM_ARG_ERROR;//引数がおかしい
    }

    //正しいサイズを書き込む
    fseek(pdma->ofp, 0, SEEK_SET);
    pdma->wh.bytes = pdma->wh.pcmbytes + sizeof(WAVHEADER) - 8;
    fwrite(&pdma->wh, sizeof(WAVHEADER), 1, pdma->ofp);
    free_DownMixArg(pdma);//開放

    return DM_NOERROR;
}


//DownMixArg構造体を開放
void free_DownMixArg(DownMixArg *pdma)
{
    if (pdma->in) free(pdma->in);
    if (pdma->out) free(pdma->out);
    if (pdma->ifp) fclose(pdma->ifp);
    if (pdma->ofp) fclose(pdma->ofp);
    pdma->in = pdma->out = NULL;
    pdma->ifp = pdma->ofp = NULL;
}

//クリップを防ぐ
short clip(int i)
{
    if (i > 32767) {
        return 32767;
    } else if (i < -32768) {
        return -32768;
    }
    return i;
}

//mesを表示して終了
void err_exit(char *mes, int code)
{
    puts(mes);
    exit(code);
}

//"アプリのディレクトリ+DEFAULT_OUTFILENAME"をbufに返す
void defaultname(char *buf)

{
    char modnam[333], drv[333], dir[333];
    
    GetModuleFileName(GetModuleHandle(NULL), modnam, 333);
    _splitpath(modnam, drv, dir, NULL, NULL);
    sprintf(buf, "%s%s%s", drv, dir, DEFAULT_OUTFILENAME);
}


int main(int argc, char *argv[])
{
    DownMixArg dma;
    
    if (argc < 2) err_exit("引数?", 1);

    if (init_downmix(&dma, argv[1], argv[2], SAMPLECOUNT) != DM_NOERROR) {
        err_exit("初期化失敗", 2);
    }
    while (downmix62(&dma) == DM_NOERROR) {
        printf("out %8.3f MB ( %5d sec)\r", 
            dma.wh.pcmbytes/(1024.*1024), dma.wh.pcmbytes/dma.wh.nAvgBytesPerSec);
    }
    end_downmix(&dma);
    puts("\nおわり");
    
    return 0;
}

Thumbs.dbからjpegファイルを抽出する

難しいことを考えずにjpgのマジックナンバー(0xFF, 0xD8)を検出してファイルに書き出すだけです。

#include <stdio.h>

int main(int argc, char **argv)
{
    FILE *ifp, *ofp=NULL;
    int c = 0 , d = 0, n = 0;
    char out[300];
    
    if ((ifp = fopen(argv[1], "rb")) == NULL) return 1;
    
    while ((c = fgetc(ifp)) != EOF) {
        if (d == 0xFF && c == 0xD8) {//jpegのマジックナンバー
            //前回のファイルを閉じる
            if (ofp!=NULL) {
                printf("抽出 : %s[%dbytes]\n", out,ftell(ofp));
                fclose(ofp);
            }

            //新たにファイルを作る
            sprintf(out, "%s-%04d.jpg", argv[1], n++);
            ofp = fopen(out, "wb");
            if (ofp!=NULL) fputc(d, ofp);
        }
        
        if (ofp!=NULL) fputc(c, ofp);
        d = c;//
    }
    
    if (ofp!=NULL) fclose(ofp);
    fclose(ifp);
    
    return 0;
}