理系学生日記

おまえはいつまで学生気分なのか

QuickTimeの日時情報は32-bit integerであり未設定の場合は1904/01/01 00:00:00 (UTC)扱いになる

メタデータであるCreation Timeを削除したQuickTimeファイルから、Creation Dateを読み出そうとすると例外が発生するように実装しました。 使用しているライブラリはこちらです。

public class QuickTimeCreationTimeKeyMapper implements MovieFileKeyMapper {
    @Override
    public String map(File movieFile) {
        Metadata metadata = null;
        try {
            // 録画日時の抽出
            metadata = ImageMetadataReader.readMetadata(movieFile);
            Collection<QuickTimeDirectory> directories = metadata.getDirectoriesOfType(QuickTimeDirectory.class);
            Date creationDate = directories.stream()
                    .map(d -> d.getDate(QuickTimeDirectory.TAG_CREATION_TIME))
                    .filter(d -> d != null)
                    .findAny()
                    .orElseThrow(() -> new MovieParseException(
                            String.format("Error occurred in parsing %s metadata", movieFile.getPath()),
                            movieFile));

            // snip
        } catch (ImageProcessingException | IOException e) {
            throw new MovieParseException("Failed to generate Object Key for S3", movieFile, e);
        }
    }
}

しかし、Creation Timeを削除したファイルに対して例外が発生することをテストしても、当該のテストがなぜかFailするという問題。 どうも、CreationTimeが格納されていない場合であっても、d.getDate(QuickTimeDirectory.TAG_CREATION_TIME)はnullを返さないようです。

QuickTimeにおける日時情報の仕様

「Creation Timeが格納されていない」というのはQuickTimeフォーマットではどういうことを意味するのか、QuickTimeの仕様を確認しました。 QuickTimeでは日時情報を、1904/01/01からの経過秒として32-bit integerで表現するとあります。また、それはUTCとして保持すべきとされています。

QuickTime movies store date and time information in Macintosh date format: a 32-bit value indicating the number of seconds that have passed since midnight January 1, 1904. This value does not specify a time zone. Common practice is to use local time for the time zone where the value is generated. It is strongly recommended that all calendar date and time values be stored using UTC time, so that all files have a time and date relative to the same time zone.

Basic Data Types

必要な実装

従って「Creation Time」が格納されていない場合は、QuickTimeフォーマット上では32-bit integerとして0が格納されているでしょう。 Javaだと1904/01/01 00:00:00.0Dateが返却されると想定できます。

この情報を元に、有効日時が返却されているかを確認するPredicateとしてhasValidDateを用意します。

public class QuickTimeCreationTimeKeyMapper implements MovieFileKeyMapper {

    protected static final Date ZERO_VALUE_DATE;
    static {
        ZonedDateTime utcDateTime = ZonedDateTime.of(1904,1,1,0,0,0,0, ZoneId.of("UTC"));
        ZERO_VALUE_DATE = Date.from(utcDateTime.toInstant());
    }

    protected Logger logger = LoggerFactory.getLogger(QuickTimeCreationTimeKeyMapper.class);

    protected boolean hasValidDate(final Date date) {
        if (date == null) {
            return false;
        }

        // QuickTime仕様におけるCreation Timeは、1904/01/01 (UTC)からの経過秒を32-bit integerで表現するため、
        // Creation Timeが格納されていない場合は、1904/01/01 00:00:00(UTC)が返却される
        if (ZERO_VALUE.equals(date)) {
            return false;
        }
        return true;
    }
    
    // snip
}

このhasValidDateでfilterを行うことで、無事にCreation Timeが格納されていないファイルに対して例外が発生するようになりました。

    Date creationDate = directories.stream()
        .map(d -> d.getDate(QuickTimeDirectory.TAG_CREATION_TIME))
        .filter(d -> hasValidDate(d))
        .findAny()
        .orElseThrow(() -> new MovieParseException(
            String.format("Error occurred in parsing [%s]'s creation date", movieFile.getPath()),
            movieFile));

# Creation Time未設定データの準備

$ exiftool -CreationDate= -CreateDate= -ModifyDate= target_date.mov