ALGOBIT > 離散的な気まぐれ

2010/06/17

シェルから Cocoa (2)

Filed under: 離散的な気まぐれ — タグ: , , , — Kohyama @ 13:59

UNIX で 100行程度で記述できる処理をするなら, シェルスクリプトを書くか, C のソースを vi で書いて, そのままシェルからコンパイルして実行することが多い. (多かった.)
C で書くことは, UNIX C 開発の経験値を上げるという意義を含んでいる. (含んでいた.)

その経験自体に意味がある人にとっては, 目の前の処理のための最適手法でなくても採用する意味がある. (これはもちろん他の言語で書いたり, わざわざ IDE を起動する場合にも当てはまる. それを経験すること自体に価値があるのだ.)

さて最近主な環境として採用した MacOS X ですが, シェルスクリプトはそのまま使えるので, その範囲を越える場合は Objective-C 言語で Cocoa フレームワークを使います. アップルスクリプトはまた今度.
実はここには, MacOS X 開発の経験値をあげるだけでなく, iPhone, iPad 開発の技能に直結するという隠れた(隠れてないか)意義があります.
ただし, 100行以下の処理なら, XCode 立ち上げるのは面倒なので, vi で編集して, シェルから cc して, 実行.

口上はともかく, 例えば, 目の前の作業.
同じメーカーの機器から取り込んだ写真や動画のファイル名が重複しないように,
また, ファイル名でソートした時に撮影順になるように, “YYYYmmddHHMMSS” の14桁の数字にフォーマットした「撮影日時」を含むファイル名に元のファイルのファイル名を一括変換したい.
ただし, コピーしたり, メールでファイルを送ってもらったりしている間に「ファイルの作成日時」は簡単に更新されてしまいますので, あくまで「撮影日時」です.

Windows 常用中は, VisualBasic .NET で書いたプログラム(数十行)使ってましたが, Exif のみ対応でした.

今回は Cocoa と QTKit で, JPEG に入っている Exif と, QuickTime な動画(mov とか 3gp とか) のヘッダに格納されている撮影日時を認識しましょう.

datenname.m

#import <Cocoa/Cocoa.h>
#import <QTKit/QTKit.h>

int
main(int argc, char *argv[])
{
    NSAutoreleasePool *ap = [[NSAutoreleasePool alloc] init];
    NSFileManager *fm;
    NSString *path, *newpath, *prefix, *dt, *suffix;
    NSImage *img;
    NSArray *arr;
    NSDictionary *dic;
    QTMovie *mov;
    NSError *err;
    NSDateFormatter *df;

    if (argc < 2)
        exit(1);

    /*
     * prefix and suffix should be variable
     * but now hard coded.
     */
    prefix = @"iph";
    suffix = @"";

    /*
     * to format an NSDate as an NSString of format "YYYYmmddHHMMSS"
     */
    df = [[[NSDateFormatter alloc]
        initWithDateFormat:@"%Y%m%d%H%M%S"
           allowNaturalLanguage:FALSE] autorelease];

    fm = [[NSFileManager alloc] init];
    while (0 < --argc) {
        path = [NSString stringWithCString: *++argv encoding: NSUTF8StringEncoding];
        dt = nil;
        err = nil;

        if (img = [[NSImage alloc] initWithContentsOfFile:path]) {
            if ((arr = [img representations]) &&
                (dic = [[arr objectAtIndex:0] valueForProperty: @"NSImageEXIFData"])) {
                /* now
                 *   [dic objectForKey:@"DateTimeOriginal"]
                 * returns string like "2010:05:13 12:36:45"
                 * let's retrieve colon(':') and space(' ') from this.
                 */
                dt = [[[dic objectForKey:@"DateTimeOriginal"]
                    stringByReplacingOccurrencesOfString:@":" withString:@""]
                    stringByReplacingOccurrencesOfString:@" " withString:@""];
            } else {
                NSLog(@"Can't recognize NSImage.DateTimeOriginal field of the file : %@", path);
            }

        } else if (mov = [QTMovie movieWithFile:path error:&err]) {
            if (!err) {
                /* now
                 *   [mov attributeForKey:QTMovieCreationTimeAttribute]
                 * returns the datetime as NSDate
                 * let's format it to "YYYYmmddHHMMSS"
                 */
                dt = [df stringFromDate:
                    [mov attributeForKey:QTMovieCreationTimeAttribute]];
            } else {
                NSLog(@"Can't recognize QTMovie.CreationTimeAttributes field of the file : %@",
                    path);
            }
        } else {
            NSLog(@"Can't recognize format of the file : %@", path);
        }

        if (dt != nil) {
            newpath = [[[path stringByDeletingLastPathComponent]
                stringByAppendingPathComponent: [[prefix stringByAppendingString: dt]
                    stringByAppendingString:suffix]]
                stringByAppendingPathExtension: [path pathExtension]];
            if ([fm fileExistsAtPath:newpath]) {
                NSLog(@"'%@' already exists.", newpath);
            } else {
                err = nil;
                [fm moveItemAtPath:path toPath:newpath error:&err];
                if (err)
                    NSLog(@"failed to rename '%@' to '%@'.", path, newpath);
                else
                    NSLog(@"'%@' has been renamed as '%@'.", path, newpath);
            }
        }
    }

    [ap release];
    return 0;
}

を作っておいて

cc -o datenname datenname.m -framework Cocoa -framework QTKit

でコンパイルし, datenname をパスの通っている場所にコピーしておきます.
使い方は, ファイル名を変換したいところのファイルをワイルドカードなど使って全部引数として渡して実行.

対象とする API の出版元以外が提供する書籍やサイトのサンプルソースは, 関連すると思われるソースコード断片があってもコピーペーストはしません, 出てきたクラスやメソッドをリファレンス で確認し, 一から自分で書くようにします.

経験を貯めるのが目的の一部なので, 自分のソースは再利用しても良いけど, 人のソースは貼付けません. 文法, クラス, メソッドで分からないものがあれば, リファレンスで調べて, できるだけ一から書きます.
部分的にコードをそのまま利用する場合でも, なるべくコピーペーストはせず, 自分の手で入力し, また, 自分が変更してもよい識別子と, API などが提供する識別子を明確に区別するために, 変数名などはなるべく再命名します.

しかし現時点では, Objective-C のメモリ管理モデルがまだ理解できていないので, NSAutoreleasePool まわりは, 参考にしたソース断片を書き写しただけです. これでいいのか分かりません.
次の課題とします.

Related Posts:

コメントをどうぞ

*

Copyright © 2010 Yoshinori Kohyama All Rights Reserved.