TorasenLab@はてな このページをアンテナに追加 RSSフィード

2011-04-29

ALAssetsLibraryについて

| 14:50 | ALAssetsLibraryについてを含むブックマーク ALAssetsLibraryについてのブックマークコメント

 以下は私がWebや書籍から集めた情報や、私が試行錯誤した経験により、ALAssetsLibraryについてまとめたものです。

そのため、間違いが含まれている可能性があります。何かしらの間違いを見つけた方はご指摘願います。

はじめに

 ALAssetsLibraryとはiOS4で追加された、iPhone/iPad/iPodの写真/映像フォルダにアクセスするためのフレームワークです。

 私はiOS4がリリースされた頃からiOSプログラミングを始めたため、iOS3以前については良く知りませんが、iOS3以前ではファイルパスを指定し、写真/映像フォルダに直接アクセスしていたようです。しかし現在はファイルに直接アクセスするとAppleの審査が通らないようです。そのため現在iPhone/iPad/iPodの写真・映像フォルダにアクセスするためにはALAssetsLibraryを使用しないといけません。

 ALAssetsLibraryを使用すると画像データのEXIFデータなども取得でき、便利です。

 ALAssetsLibraryがリリースされてから半年程度経ちますが、ALAssetsLibraryに関するサンプルコードやチュートリアルなどはかなり少ないです。そのため今回ALAssetsLibraryについてまとめることにしました。

サンプルコード

MYAssetsAccessor.h

#import <UIKit/UIKit.h>


@class ALAssetsLibrary;


@interface MYAssetsAccessor : NSObject {
 @private
  ALAssetsLibrary *assetsLibrary_;
}


- (void)loadUrlsWithCallbackTarget:(id)anObject
                    callbackMethod:(SEL)aSelector;
- (NSDictionary *)metadataOfAssetIdentifiedByUrl:(NSURL *)url;
- (UIImage *)thumbnailOfAssetsIdentifiedByUrl:(NSURL *)url;
- (UIImage *)fullResolutionImageOfAssetsIdentifiedByUrl:(NSURL *)url;


+ (void)addObserverForAssetsLibraryChange:(id)anObserver
                                 selector:(SEL)aSelector;
+ (void)removeObserverForAssetsLibraryChange:(id)anObserver;


// to understand how the enumeration works.
- (void)enumerationTest_;

@end

MYAssetsAccessor.m

#import <AssetsLibrary/AssetsLibrary.h>  // require AssetsLibrary.framework
#import "MYAssetsAccessor.h"


@implementation MYAssetsAccessor

- (id)init {
  assetsLibrary_ = [[ALAssetsLibrary alloc] init];
  return self;
}


- (void)dealloc {
  [assetsLibrary_ release];
  [super dealloc];
}


- (void)loadUrlsWithCallbackTarget:(id)anObject
                    callbackMethod:(SEL)aSelector {
  id callbackTarget = [anObject retain];
  SEL callbackMethod = aSelector;
  
  NSMutableArray *urls = [[NSMutableArray alloc] init];
  
  void (^assetResultBlock)(ALAsset *, NSUInteger, BOOL *) =
  ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
    if (!*stop && asset) {
      ALAssetRepresentation *defaultRepresentation = [asset defaultRepresentation];
      [urls addObject:[defaultRepresentation url]];
    }
  };
	
  void (^resultBlock)(ALAssetsGroup *, BOOL *) =
  ^(ALAssetsGroup *group, BOOL *stop) {
    if (*stop || !group) {
      [callbackTarget performSelector:callbackMethod withObject:urls];
    } else {
      [group setAssetsFilter:[ALAssetsFilter allAssets]];
      [group enumerateAssetsUsingBlock:assetResultBlock];
    }
  };
  
  void (^failureBlock)(NSError *) =
  ^(NSError *error) {
    NSLog(@"exception in enumerating assets. %@", error);
    [callbackTarget performSelector:callbackMethod withObject:urls];
  };
  
  ALAssetsGroupType targetGroupType = ALAssetsGroupAll;
  [assetsLibrary_ enumerateGroupsWithTypes:targetGroupType
                                usingBlock:resultBlock
                              failureBlock:failureBlock];
  
  [urls release];
  [callbackTarget release];
}


- (NSDictionary *)metadataOfAssetIdentifiedByUrl:(NSURL *)url {
  __block NSDictionary *metadata = nil;
  [assetsLibrary_ assetForURL:url
                  resultBlock:^(ALAsset *asset) {
                    ALAssetRepresentation *defaultRepresentation = [asset defaultRepresentation];
                    metadata = [[defaultRepresentation metadata] copy];
                  }
                 failureBlock:^(NSError *error) {
                   NSLog(@"exception in accessing assets by url. %@", error);                      
                 }];
  return [metadata autorelease];
}


- (UIImage *)thumbnailOfAssetsIdentifiedByUrl:(NSURL *)url {
  __block UIImage *thumbnail = nil;
  [assetsLibrary_ assetForURL:url
                  resultBlock:^(ALAsset *asset) {
                    thumbnail = [[UIImage alloc] initWithCGImage:[asset thumbnail]];
                  }
                 failureBlock:^(NSError *error) {
                   NSLog(@"exception in accessing assets by url. %@", error);                      
                 }];
  return [thumbnail autorelease];
}


- (UIImage *)fullResolutionImageOfAssetsIdentifiedByUrl:(NSURL *)url {
  __block UIImage *fullResolutionImage = nil;
  [assetsLibrary_ assetForURL:url
                  resultBlock:^(ALAsset *asset) {
                    ALAssetRepresentation *defaultRepresentation = [asset defaultRepresentation];
                    fullResolutionImage = [[UIImage alloc] initWithCGImage:[defaultRepresentation fullResolutionImage]];
                  }
                 failureBlock:^(NSError *error) {
                   NSLog(@"exception in accessing assets by url. %@", error);                      
                 }];
  return [fullResolutionImage autorelease];
}


+ (void)addObserverForAssetsLibraryChange:(id)anObserver
                                 selector:(SEL)aSelector {
  NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
  [defaultCenter addObserver:anObserver
                    selector:aSelector
                        name:ALAssetsLibraryChangedNotification
                      object:nil];
}


+ (void)removeObserverForAssetsLibraryChange:(id)anObserver {
  NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
  [defaultCenter removeObserver:anObserver
                           name:ALAssetsLibraryChangedNotification
                         object:nil];
}


// to understand how the enumeration works.
- (void)enumerationTest_ {
  void (^assetResultBlock)(ALAsset *, NSUInteger, BOOL *) =
  ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
    if (*stop) {
      NSLog(@"stopped enumerating assets.");
    } else if (!asset) {
      NSLog(@"the end of the group.");
    } else {
      NSDictionary *urls = [asset valueForProperty:ALAssetPropertyURLs];
      NSLog(@"%@", urls);
    }
  };
	
  void (^resultBlock)(ALAssetsGroup *, BOOL *) =
  ^(ALAssetsGroup *group, BOOL *stop) {
    if (*stop) {
      NSLog(@"stopped enumerating groups.");
    } else if (!group) {
      NSLog(@"the end of enumation.");
    } else {
      NSString *groupName = [group valueForProperty:ALAssetsGroupPropertyName];
      
      ALAssetsFilter *photosFilter = [ALAssetsFilter allPhotos];
      [group setAssetsFilter:photosFilter];
      NSInteger numberOfPhotos = [group numberOfAssets];
      NSLog(@"%d photos in %@", numberOfPhotos, groupName);
      [group enumerateAssetsUsingBlock:assetResultBlock];
      
      ALAssetsFilter *videosFilter = [ALAssetsFilter allVideos];
      [group setAssetsFilter:videosFilter];
      NSInteger numberOfVideos = [group numberOfAssets];
      NSLog(@"%d videos in %@", numberOfVideos, groupName);
      [group enumerateAssetsUsingBlock:assetResultBlock];
      
      ALAssetsFilter *allFilter = [ALAssetsFilter allAssets];
      [group setAssetsFilter:allFilter];
      NSInteger numberOfAssets = [group numberOfAssets];
      NSLog(@"%d assets in %@", numberOfAssets, groupName);
      [group enumerateAssetsUsingBlock:assetResultBlock];
    }
  };
  
  void (^failureBlock)(NSError *) =
  ^(NSError *error) {
    NSLog(@"exception in enumerating assets. %@", error);
  };
	
  // ALAssetsGroupType targetGroupType = ALAssetsGroupLibrary |
  //   ALAssetsGroupAlbum |
  //   ALAssetsGroupEvent |
  //   ALAssetsGroupFaces |
  //   ALAssetsGroupSavedPhotos;
  ALAssetsGroupType targetGroupType = ALAssetsGroupAll;
  [assetsLibrary_ enumerateGroupsWithTypes:targetGroupType
                                usingBlock:resultBlock
                              failureBlock:failureBlock];
  
  // the result will be:
  // 2011-04-28 18:38:40.939 MyProject[869:707] 4 photos in Photo Library
  // 2011-04-28 18:38:40.964 MyProject[869:707] {
  //   "public.jpeg" = "assets-library://asset/asset.jpg?id=104&ext=jpg";
  // }
  // ...
  // 2011-04-28 18:38:40.989 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:40.993 MyProject[869:707] 0 videos in Photo Library
  // 2011-04-28 18:38:40.997 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:41.003 MyProject[869:707] 4 assets in Photo Library
  // 2011-04-28 18:38:41.008 MyProject[869:707] {
  //   "public.jpeg" = "assets-library://asset/asset.jpg?id=104&ext=jpg";
  // }
  // ...
  // 2011-04-28 18:38:41.030 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:41.039 MyProject[869:707] 25 photos in Camera Roll
  // 2011-04-28 18:38:41.053 MyProject[869:707] {
  //   "public.jpeg" = "assets-library://asset/asset.JPG?id=1000000021&ext=JPG";
  // }
  // ...
  // 2011-04-28 18:38:41.273 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:41.277 MyProject[869:707] 4 videos in Camera Roll
  // 2011-04-28 18:38:41.285 MyProject[869:707] {
  //   "com.apple.quicktime-movie" = "assets-library://asset/asset.MOV?id=1000000004&ext=MOV";
  // }
  // ...
  // 2011-04-28 18:38:41.314 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:41.319 MyProject[869:707] 29 assets in Camera Roll
  // 2011-04-28 18:38:41.327 MyProject[869:707] {
  //   "public.jpeg" = "assets-library://asset/asset.JPG?id=1000000021&ext=JPG";
  // }
  // ...
  // 2011-04-28 18:38:41.569 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:41.573 MyProject[869:707] the end of enumation.
}

@end

AppDelegate_iPhone.h

#import <UIKit/UIKit.h>


@class MYAssetsAccessor;


@interface AppDelegate_iPhone : NSObject <UIApplicationDelegate> {
  UIWindow *window_;
  MYAssetsAccessor *myAssetsAccessor_;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

AppDelegate_iPhone.m

#import "AppDelegate_iPhone.h"
#import "MYAssetsAccessor.h"


@interface AppDelegate_iPhone(Private)

- (void)assetLibraryDidChange_:(NSNotification *)aNotification;
- (void)assetUrlsDidLoad_:(NSArray *)urls;

@end


@implementation AppDelegate_iPhone
@synthesize window = window_;

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
  [self.window makeKeyAndVisible];
  myAssetsAccessor_ = [[MYAssetsAccessor alloc] init];
  [MYAssetsAccessor addObserverForAssetsLibraryChange:self
                                             selector:@selector(assetLibraryDidChange_:)];
  [myAssetsAccessor_ enumerationTest_];
  [myAssetsAccessor_ loadUrlsWithCallbackTarget:self
                                 callbackMethod:@selector(assetUrlsDidLoad_:)];
  return YES;
}


- (void)applicationWillTerminate:(UIApplication *)application {
  [MYAssetsAccessor removeObserverForAssetsLibraryChange:self];
}


- (void)dealloc {
  [myAssetsAccessor_ release];
  [window_ release];
  [super dealloc];
}

@end


@implementation AppDelegate_iPhone(Private)

- (void)assetLibraryDidChange_:(NSNotification *)aNotification {
  NSLog(@"asset library changed. %@", aNotification);
  // TODO reload urls
}


- (void)assetUrlsDidLoad_:(NSArray *)urls {
  NSLog(@"%d urls, %@", [urls count], urls);
  if ([urls count] == 0) {
    return;
  }
  NSURL *url = [urls objectAtIndex:30];
  NSDictionary *metadata = [myAssetsAccessor_ metadataOfAssetIdentifiedByUrl:url];
  NSLog(@"metadata of %@ is %@", url, metadata);
  UIImage *thumbnail = [myAssetsAccessor_ thumbnailOfAssetsIdentifiedByUrl:url];
  NSLog(@"thumbnail of %@ is %@", url, thumbnail);
  UIImage *fullResolutionImage = [myAssetsAccessor_ fullResolutionImageOfAssetsIdentifiedByUrl:url];
  NSLog(@"full resolution image of %@ is %@", url, fullResolutionImage);
}

@end

サンプルコード概要

 MYAssetsAccessorはALAssetsLibraryを利用した、画像/映像フォルダへアクセスするためのクラスです。AppDelegate_iPhoneはMYAssetsAccessorを利用したコードです。

 MYAssetsAccessorのloadUrlsWithCallbackTarget:callbackMethod:で画像/映像フォルダ中の画像/映像を表すURLの配列を取得します。メソッド中のenumerateGroupsWithTypes:usingBlock:failureBlock:は非同期で実行されるため、URLの取得が終了するもしくは何らかのエラーが生じた場合、指定したcallbackメソッドを呼び出します。なお、ブロック内で*stop = YES;とすると、走査を終了させることもできます。

 URLの一覧が取得できれば後はメタデータや、サムネイルを取得したりといろいろできます。

 URLは画像/映像フォルダに何らかの変更が生じた場合、変更される事があるので、ALAssetsLibraryChangeNotificationを監視しておきます。

 MYAssetsAccessorには動作確認用のenumerationTest_メソッドを用意しています。画像/映像フォルダを走査して、URL等をログに書き出します。


サンプルコードはMITライセンスです。

 MYAssetsAccessor.h 直

 MYAssetsAccessor.m 直

 AppDelegate_iPhone.h 直

 AppDelegate_iPhone.m 直

注意点

ALAssetsLibraryのインスタンスのメソッドはメインスレッドで呼ぶ方が良い

 ALAssetsLibraryのインスタンスのメソッドを初めて呼び出すと、次のようなアラートが表示されます。

f:id:torasenriwohashiru:20110429135321p:image

 メインスレッドで呼び出した場合は上記のアラートが表示されます。

 しかし、メインスレッド以外で呼び出した場合は上記のアラートが表示されず、ユーザの入力待ち状態のままになり、ALAssetsLibraryに関する処理が一向に進まなくなります。

 なお、上記のアラートでユーザがDon't Allowを選択すると写真フォルダにアクセスできず、failureBlockが呼び出されます。

ALAssetsLibraryのインスタンスのメモリ消費量は写真/映像フォルダ内のコンテンツ量に応じて増える

 画像フォルダに10枚しか写真がない場合と、画像フォルダに10,000枚の写真がある場合とでは、ALAssetsLibraryのインスタンスのメモリ消費量がかなり違います。検索用インデックスでも作っているのでしょうか。

 画像/映像フォルダ内のコンテンツ量が増えるにつれ、アプリケーションで使用できるメモリ量が少なくなってしまいます。動作保障コンテンツ数などを決める必要があるのかもしれません。iPhone/iPad/iPodのデフォルトの写真ビューアも画像が80,000枚程度保存されていると強制終了するようになりますし。

ALAssetsLibraryChangeNotificationはバックグラウンド状態では受信できない

 1. 対象のアプリケーションをバックグラウンド状態にする

 2. 他のアプリケーションを起動/フォアグラウンド状態にする

 3. 他のアプリケーションで画像/映像フォルダに変更が生じる(新たな画像を追加する、など)

 4. 他のアプリケーションを終了/バックグラウンド状態にする

 5. 対象のアプリケーションをフォアグラウンド状態にする

 上記のようなアプリケーションの切り替えなどを行った場合、対象のアプリケーションがALAssetsLibraryChangeNotificationを受信するのは5のタイミングです。3の画像/映像フォルダに変更が生じた際ではなく、5の対象のアプリケーションがフォアグラウンド状態になったときです。

iTunesの同期ではALAssetsLibraryChangeNotificationは発生しない

 iTunesの同期により画像/映像フォルダに変更が生じても、ALAssetsLibraryChangeNotificationは発生しません。

iTunesで同期すると画像のEXIFは消失する

 PCからiTunes経由でiPhone/iPad/iPodに画像を転送した場合、転送元のPC上の画像にEXIFが付属していたとしても、iPhone/iPad/iPodに転送される際に削除されるようです。

 下記参考文献のiOS4プログラミングブックの中でも次のように述べられています。

PC上のオリジナル画像がExif情報付きだったとしても、同期するタイミングで情報が削られているようです。

 ただし、iPhone/iPad/iPodのカメラで撮った写真に関しては、iTuneの同期でEXIFは削除されません。おそらくApple独自のメタデータ形式などがあるのでしょう。

iTunesで同期すると画像はJpegとして保存される

 ALAssetsLibraryと直接関係はないですが、iTunesで同期すると、bmpもpngもjpegに変換されてiPhone/iPad/iPod上に保存されるようです。

画像/映像の読み込みと新規書き込みのみ可能

 ALAssetsLibraryでは、画像/映像の読み込みと新規書き込みのみが可能で、削除や修正/上書きなどはできないようです。

参考

公式リファレンス

ALAssetsLibrary Class Reference

 Apple公式のリファレンスです。

読み込み

iOS 4の新機能13選&AssetsLibraryで作る画像ビューア (2/4):SDKで始めるiPad/iPhoneアプリ開発の勘所(終) - @IT

 ALAssetsLibraryを使用した画像ビューアの作成方法。ソースコードのダウンロードもできます。

no title

 GPSデータの読み込みなど。

書き込み

[AssetsLibrary] フォトライブラリの写真にExif情報を付けて保存する - Ni chicha, ni limona - 平均から抜けられない僕 - iPhoneアプリ開発グループ

 画像データをEXIF付きで保存する。

注意点

[AssetsLibrary] Assets Libraryフレームワークを使うときの注意点 - Ni chicha, ni limona - 平均から抜けられない僕 - iPhoneアプリ開発グループ

書籍
iOS4プログラミングブック

iOS4プログラミングブック

 iOS4で追加された機能に焦点をあてた本。多くはありませんがALAssetsLibraryについての記述があります。

私が知っている限り、ALAssetsLibraryについて書かれている日本語の本はこれだけです。

2011-04-07

docx, xlsx, pptxファイルからテキストをXMLとして抽出する

| 23:56 | docx, xlsx, pptxファイルからテキストをXMLとして抽出するを含むブックマーク docx, xlsx, pptxファイルからテキストをXMLとして抽出するのブックマークコメント

MS Office 2007以降のファイル(.docx, .xlsx, .pptx)はZip圧縮されたXMLファイルなので、ファイルからのテキスト抽出などが簡単にできます。

f:id:torasenriwohashiru:20110407235131p:image

.pptxファイルをZipファイルとして展開すると上記のようになります。

各スライドがXMLファイルとして保存されています。

.docx, .xlsx, .pptxはそれぞれディレクトリ構造が若干違います。

読み取り、もしくは書き込みパスワードが設定されている場合はZipファイルとして解凍はできないようです。

import os.path
import zipfile


def _extract_xmls_from_msxml_base(filepath, prefix):
    xmls = []
    zf = None
    try:
        zf = zipfile.ZipFile(filepath, 'r')
        for name in zf.namelist():
            if name.startswith(prefix):
                xmls.append(zf.read(name))
    except zipfile.BadZipfile, e:
        # for locked files
        print e
    finally:
        if zf is not None:
            zf.close()
    return xmls


def extract_xmls(filepath, ext=None):
    if ext is None:
        root, ext = os.path.splitext(filepath)
    ext = ext.lower()
    prefix = None
    if ext == '.pptx':
        prefix = 'ppt/slides/slide'
    elif ext == '.xlsx':
        prefix = 'xl/worksheets/sheet'
    elif ext == '.docx':
        prefix = 'word/document'

    if prefix is not None:
        return _extract_xmls_from_msxml_base(filepath, prefix)
    return None


def _test():
    print extract_xmls('test.pptx')
    

if __name__ == '__main__':
    _test()

2011-04-02

不正な(malformed)HTMLテキストを修正する

| 00:36 | 不正な(malformed)HTMLテキストを修正するを含むブックマーク 不正な(malformed)HTMLテキストを修正するのブックマークコメント

Web上のHTMLテキストには不正な(malformed)形式を持っている場合があります。

malformedなHTMLテキストにに対してはBeautifulSoupやHTMLParserなどがうまく機能しません。

例えば以下の3つのパターンはBeautifulSoupでは例外が生じます。

1. タグの閉じ>が無い

<div class="invalid_start_tag" 
  <a src="http://...">something</a>
</div>

2. 閉じタグに属性がある

</div class="invalid_end_tag">

3. scriptタグ中に閉じタグがある

<script language="javascript"><!--
  function splitTag() { return '</scr' + 'ipt>'; }
  //-->
</script>

Pythonの勉強ついでにパターン1,2を修正する関数formalize_htmlを作成しました。

パターン3については修正できていません。ただしHTMLテキストからのテキスト抽出などが目的であれば、scriptタグやコメントタグごと削除すれば問題は無いと思います。

勉強のために、doctestというものを試しに使用しています。

import re
from sgmllib import SGMLParser


_RE_SCRIPT = re.compile(r'<script.*?>.*?</script>',
                       re.MULTILINE | re.DOTALL | re.IGNORECASE)
def _remove_script_tags(htmltext):
    return _RE_SCRIPT.sub('', htmltext)


_RE_COMMENT = re.compile(r'<!--.*?-->', re.MULTILINE | re.DOTALL)
def _remove_comment_tags(htmltext):
    return _RE_COMMENT.sub('', htmltext)


_RE_STYLE = re.compile(r'<style.*?>.*?</style>',
                      re.MULTILINE | re.DOTALL | re.IGNORECASE)
def _remove_style_tags(htmltext):
    return _RE_STYLE.sub('', htmltext)


def _escape_htmltext(htmltext):
    escape_patterns = (('<', '&lt;'), ('>', '&gt;'),
                       ('\'', '&#39;'), ('"', '&quot;'))
    escaped_text = htmltext
    for from_text, to_text in escape_patterns:
        escaped_text = escaped_text.replace(from_text, to_text)
    return escaped_text


class _HTMLFormalizer(SGMLParser):
    def __init__(self):
        self._data = []
        SGMLParser.__init__(self)
    def unknown_starttag(self, tag, attributes):
        self._data.append('<{0}'.format(tag))
        for key, value in attributes:
            self._data.append(' {0}="{1}"'.format(key, _escape_htmltext(value)))
        self._data.append('>')
    def unknown_endtag(self, tag):
        tag = tag.split()[0].lower()
        self._data.append('</{0}>'.format(tag))
    def handle_data(self, data):
        self._data.append(_escape_htmltext(data))
    def append_data(self, data):
        self._data.append(data)
    def get_data(self):
        return self._data


def formalize_html(htmltext, 
                   keep_newlines=True,
                   remove_scripts=True,
                   remove_comments=True,
                   remove_styles=True):
    '''
    >>> malformed_htmltext = """
    ... <html>
    ...   <meta>
    ...   <head>
    ...     <title>TITLE</title>
    ...     <script language="javascript"><!--
    ...       function splitTag() { return '</scr' + 'ipt>'; }
    ...       function max(a, b) { return a > b ? a : b; }
    ...       //-->
    ...     </script>
    ...   </head>
    ...   <body class="invalid_start_tag"
    ...       id="id_invalid"
    ...     <img src="http://...">
    ...     <div>
    ...       a > b ? a : b;
    ...     </div class="invalid_end_tag">
    ...   </body>
    ... </html>"""
    >>> print formalize_html(malformed_htmltext)
    <BLANKLINE>
    <html>
      <meta>
      <head>
        <title>TITLE</title>
    <BLANKLINE>
      </head>
    <BLANKLINE>
    <BLANKLINE>
    <body class="invalid_start_tag" id="id_invalid"><img src="http://...">
        <div>
          a &gt; b ? a : b;
        </div>
      </body>
    </html>
    <BLANKLINE>
    '''

    text = htmltext
    if remove_scripts:
        text = _remove_script_tags(text)
    if remove_comments:
        text = _remove_comment_tags(text)
    if remove_styles:
        text = _remove_style_tags(text)
    formalizer = _HTMLFormalizer()
    for line in text.splitlines():
        formalizer.feed(line)
        if keep_newlines:
            formalizer.append_data('\n')
    formalizer.close()
    return ''.join(formalizer.get_data())


def _test():
    import doctest
    doctest.testmod()
    

if __name__ == '__main__':
    _test()

2011-03-20

OpenOffice形式に変換する

| 11:05 | OpenOffice形式に変換するを含むブックマーク OpenOffice形式に変換するのブックマークコメント

OpenOffice形式(ODF:.odt, .ods, .odpなど)はZip圧縮された複数のXMLファイルなので、テキストを抽出したりするのが容易です。

一方、以前のMS Office形式(.doc, .xls, .pptなど)はバイナリ形式なのでテキスト抽出などは困難です。

なのでOpenOffice形式への変換方法などを調べました。

目的

MS Office形式(.doc, .xls, .pptなど)からOpenOffice形式(ODF:.odt, .ods, .odpなど)に変換したい。

欲しい機能

  • Linux(Ubuntu)で使用できる
  • バッチ処理ができる。(GUIを操作したくない)
  • 新規にツールなどをインストールしたくない
  • 処理が速い

解決策

下記の参考サイトを参考にして、OpenOfficeを使用してファイル形式を変換することにしました。

OpenOfficeにファイル変換用マクロを登録し、そのマクロを呼び出してファイル形式を変換します。

以下の2つのファイルを編集して、ファイル変換用マクロを登録します。

1. ~/.openoffice.org/3/user/basic/Standard/Export.xba

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Export" script:language="StarBasic">
Function MakePropertyValue( Optional cName As String, Optional uValue ) _
   As com.sun.star.beans.PropertyValue
   Dim oPropertyValue As New com.sun.star.beans.PropertyValue
   If Not IsMissing( cName ) Then
      oPropertyValue.Name = cName
   EndIf
   If Not IsMissing( uValue ) Then
      oPropertyValue.Value = uValue
   EndIf
   MakePropertyValue() = oPropertyValue
End Function


Sub SaveAsOOO( cInFile, cOutFile )
    On Error Goto ErrorHandler
    cInURL = ConvertToURL( cInFile )
    oDoc = StarDesktop.loadComponentFromURL( cInURL, &quot;_blank&quot;, 0, _
               Array( MakePropertyValue( &quot;Hidden&quot;, True ), ) )
    cOutURL = ConvertToURL( cOutFile )
    oDoc.storeAsURL( cOutURL, Array() )
    oDoc.close( True )
    Exit Sub
    
ErrorHandler:
	Resume Next
End Sub

</script:module>

下記の参考サイトのソースをベースに作成しました。SaveAsOOOにはエラーハンドラを追加しました。これを追加しておかないと、ファイルが見つからないなど、何らかのエラーが生じた際にメッセージボックスが表示されてしまい、バッチ処理には向きません。

2. ~/.openoffice.org/3/user/basic/Standard/script.xlb

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE library:library PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "library.dtd">
<library:library xmlns:library="http://openoffice.org/2000/library" library:name="Standard" library:readonly="false" library:passwordprotected="false">
 <library:element library:name="Export"/>
</library:library>

登録したマクロをシェルから呼ぶには以下のようにします。

ooffice -invisible -nologo 'macro:///Standard.Export.SaveAsOOO("/tmp/example.ppt", "/tmp/example.odp")'

仕様かどうかはわかりませんが、-nologoを付けないと処理がバックグラウンドで行われるようです。

ソースコード

上記のマクロを呼び出すpythonコードを作成しました。

import os, stat


def convert_from(from_path, to_path):
    updated_at = os.stat(to_path)[stat.ST_MTIME] if os.path.exists(to_path) else None
    command = 'ooffice -invisible -nologo '\
        '\'macro:///Standard.Export.SaveAsOOO("{0}", "{1}")\''
    # TODO escape from_path and to_path
    os.system(command.format(from_path, to_path))
    if not os.path.exists(to_path):
        raise Exception, 'Failed to convert to OpenOffice format, not exists'
    if os.stat(to_path)[stat.ST_MTIME] == updated_at:
        raise Exception, 'Failed to convert to OpenOffice format, not updated'


def _test():
    import tempfile
    tempfd, temppath = tempfile.mkstemp()
    convert_from('/tmp/validfile.ppt', temppath)
    os.remove(temppath)

    tempfd, temppath = tempfile.mkstemp()
    #convert_from('/tmp/invalidfile.ppt', temppath)
    os.remove(temppath)

    temppath = '/tmp/out.odp'
    convert_from('/tmp/validfile.ppt', temppath)
    os.remove(temppath)

    #convert_from('/tmp/invalidfile.ppt', temppath)
    #os.remove(temppath)

if __name__ == '__main__':
    _test()

変換が正常に行われたかどうかは、リターンコードでは判別できません。

出力ファイルが存在しない、もしくは出力ファイルが更新されていないときは変換に失敗したと判断します。

参考

Moving to OpenOffice: Batch Converting Legacy Documents

サーバ上でPDFやオフィス文書からテキストを抜き出す方法あれこれ - ククログ(2010-08-02)

OpenOffice.org: バッチ処理で .odt-.doc 相互変換、PDF出力 | anobota

大久保大久保 2018/10/28 22:11 今はOfficeへの変換しかサポートしませんか?できればOpenOffice形式のODTとPDFの変換をしたいけど

2011-01-10

ALAssetsLibraryでiPhone,iPadの写真フォルダにアクセスする

| 22:21 | ALAssetsLibraryでiPhone,iPadの写真フォルダにアクセスするを含むブックマーク ALAssetsLibraryでiPhone,iPadの写真フォルダにアクセスするのブックマークコメント

//  MyAssetManager.h

#import <Foundation/Foundation.h>
#import <AssetsLibrary/AssetsLibrary.h>

@interface MyAssetsManager : NSObject 
{
    ALAssetsLibrary *assetsLibrary_;
}

- (void)addObserverForAssetsLibraryChange:(id)anObserver
                                 selector:(SEL)aSelector;
- (void)removeObserverForAssetsLibraryChange:(id)anObserver;
- (void)enumerateAssets;
@end
//  MyAssetManager.m

#import "MyAssetsManager.h"

#define MYASSETSMANAGER_ASSETSNOTIFICATION_NAME @"ALAssetsLibraryChangedNotification" 

@implementation MyAssetsManager
- (id)init
{
    assetsLibrary_ = [[ALAssetsLibrary alloc] init];
    return self;
}

- (void)dealloc
{
    [assetsLibrary_ release];
    [super dealloc];
}

- (void)addObserverForAssetsLibraryChange:(id)anObserver
                                 selector:(SEL)aSelector
{
    NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
    [defaultCenter addObserver:anObserver
                      selector:aSelector
                          name:MYASSETSMANAGER_ASSETSNOTIFICATION_NAME
						object:nil];
}

- (void)removeObserverForAssetsLibraryChange:(id)anObserver
{
    NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
    [defaultCenter removeObserver:anObserver
                             name:MYASSETSMANAGER_ASSETSNOTIFICATION_NAME
                           object:nil];
}

- (void)enumerateAssets
{
    __block NSUInteger assetCount = 0;	
	
    void (^assetResultBlock)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop)
    {
	if (*stop) {
	    NSLog(@"Stop enumerating assets.");
	} else if (!asset) {
	    NSLog(@"End of the group.");
	} else {	
             // TODO
             NSDictionary *urls = [asset valueForProperty:ALAssetPropertyURLs];
             NSLog(@"%06d:%@", assetCount, urls);
             ++assetCount;
	}
    };	
	
    void (^resultBlock)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop)
    {
        if (*stop) {
            NSLog(@"Stop enumerating groups.");
        } else if (!group) {
            NSLog(@"End of enumation.");
        } else {
            ALAssetsFilter *assetsFilter = [ALAssetsFilter allPhotos];
	   [group setAssetsFilter:assetsFilter];	
            NSInteger numberOfAssets = [group numberOfAssets];
            NSString *groupName = [group valueForProperty:ALAssetsGroupPropertyName];
            NSLog(@"%@:num:%d", groupName, numberOfAssets);
            [group enumerateAssetsUsingBlock:assetResultBlock];		
        }
    };
    
    void (^failureBlock)(NSError *) = ^(NSError *error)
    {
        NSLog(@"Exception in enumerating assets. %@", error);
    };
	
    [assetsLibrary_ enumerateGroupsWithTypes:ALAssetsGroupAll
                                  usingBlock:resultBlock
                                failureBlock:failureBlock];
}

@end
#undef MYASSETSMANAGER_ASSETSNOTIFICATION_NAME

TODOコメントあたりでメタデータの取得などいろいろできます。

フィルタや対象グループを変更することで、写真や映像などのフィルタリングが出来ます。

"ALAssetsLibraryChangedNotification"に登録することで写真フォルダの変更等を知ることができます。ただし"ALAssetsLibraryChangedNotification"ではiTunesの同期処理による写真フォルダの変更には対応できないようです。

参考

Assets Library Framework Reference

XCodeのiPhone,iPadプロジェクトの設定

| 22:20 | XCodeのiPhone,iPadプロジェクトの設定を含むブックマーク XCodeのiPhone,iPadプロジェクトの設定のブックマークコメント

XCodeでiPhone,iPad用アプリを作成する際にまず設定していること。

LOG関数の設定

// ProjectName_Prefix.pch

#ifdef DEBUG
    #define LOG(...) NSLog(__VA_ARGS__);
    #define LOG_METHOD NSLog(@"%s", __func__);
#else
    #define LOG(...) ;
    #define LOG_METHOD ;
#endif

XCodeのプロジェクト設定

  • Debug構成:
    • "プリプロセッサマクロ"にDEBUGを追加する
  • すべての構成:
    • "静的アナライザを実行"にチェックする

実行可能ファイルの環境に設定される変数の設定

  • NSZombieEnabledをYESにして追加する。
    • リリース時には削除する。

参考

iOS SDK Hacks ―プロが教えるiPhoneアプリ開発テクニック

iOS SDK Hacks ―プロが教えるiPhoneアプリ開発テクニック