やることはタイトル通りですが、内容は主にJackson
の使い方の話です。
ffprobeのオプション指定
ffprobe
は以下のように動かします。
cat ${ファイル名} | ffprobe -hide_banner -v error -print_format json -show_streams -i pipe:0
諸々指定していますが、重要なのは以下の2点です。
-
-print_format json
-> 処理結果をJSON形式で出力 -
-show_streams
-> 音、ビデオ、字幕等の各ストリーム情報を出力
Java
からの呼び出し方は以下の記事を参照してください。
呼び出し結果
呼び出し結果は以下のとおりです(長いので折りたたみます)。
呼び出し結果
{
"streams": [
{
"index": 0,
"codec_name": "h264",
"codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
"profile": "Main",
"codec_type": "video",
"codec_time_base": "752/45075",
"codec_tag_string": "avc1",
"codec_tag": "0x31637661",
"width": 1920,
"height": 1080,
"coded_width": 1920,
"coded_height": 1088,
"has_b_frames": 1,
"sample_aspect_ratio": "1:1",
"display_aspect_ratio": "16:9",
"pix_fmt": "yuv420p",
"level": 41,
"color_range": "tv",
"chroma_location": "left",
"refs": 1,
"is_avc": "true",
"nal_length_size": "4",
"r_frame_rate": "30000/1001",
"avg_frame_rate": "45075/1504",
"time_base": "1/30000",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 601600,
"duration": "20.053333",
"bit_rate": "6653705",
"bits_per_raw_sample": "8",
"nb_frames": "601",
"disposition": {
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 0,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0
},
"tags": {
"creation_time": "2014-03-30T07:09:03.000000Z",
"language": "eng",
"handler_name": "\u001fMainconcept Video Media Handler",
"encoder": "AVC Coding"
}
},
{
"index": 1,
"codec_name": "aac",
"codec_long_name": "AAC (Advanced Audio Coding)",
"profile": "LC",
"codec_type": "audio",
"codec_time_base": "1/48000",
"codec_tag_string": "mp4a",
"codec_tag": "0x6134706d",
"sample_fmt": "fltp",
"sample_rate": "48000",
"channels": 2,
"channel_layout": "stereo",
"bits_per_sample": 0,
"r_frame_rate": "0/0",
"avg_frame_rate": "0/0",
"time_base": "1/48000",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 962560,
"duration": "20.053333",
"bit_rate": "317375",
"max_bit_rate": "317625",
"nb_frames": "942",
"disposition": {
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 0,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0
},
"tags": {
"creation_time": "2014-03-30T07:09:03.000000Z",
"language": "eng",
"handler_name": "#Mainconcept MP4 Sound Media Handler"
}
}
]
}
Jacksonによるマップ
先程出したprobe結果をオブジェクトにマップしていきます。
出力はビデオ内のstream
の配列とします。
マップ先オブジェクト
今回は以下のオブジェクトにマップしていきます(@Getter
と@Setter
はlombok
を想定して書いています)。
結果の基底はstream
とした上で、video
かaudio
か(あるいはそれ以外か)で型を分けて表現しています。
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "codec_type", defaultImpl = NoClass.class)
@JsonSubTypes({
@JsonSubTypes.Type(value = FfprobeStream.VideoStream.class, name = "video"),
@JsonSubTypes.Type(value = FfprobeStream.AudioStream.class, name = "audio")
})
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter @Setter
abstract class FfprobeStream {
private Double duration;
private String codecName;
@Getter @Setter
static class VideoStream extends FfprobeStream {
private Integer width;
private Integer height;
}
@Getter @Setter
static class AudioStream extends FfprobeStream {
private String channelLayout;
}
}
解説
以下のアノテーションで、codec_type
の値によってマップ先の型を制御しています。
神オブジェクトを作ってそこにマップする手も無くはないですが、後が大変になりやすいので、マップ先の型の制御はやっておいた方がベターです。
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "codec_type", defaultImpl = NoClass.class)
@JsonSubTypes({
@JsonSubTypes.Type(value = FfprobeStream.VideoStream.class, name = "video"),
@JsonSubTypes.Type(value = FfprobeStream.AudioStream.class, name = "audio")
})
以下のアノテーションで、マップできないフィールドは無視しています。
今回は結果の内一部フィールドしか用意していないため、これを指定しなければマップできなかった場合にエラーになります。
@JsonIgnoreProperties(ignoreUnknown = true)
probe結果文字列からstreamの配列にマップする
以下のコードで、probe結果をビデオ内のstream
の配列にマップできます。
詳細はコメントの通りです。
static List<FfprobeStream> parseFfprobeResult(String ffprobeResult) throws IOException {
ObjectMapper mapper = new ObjectMapper();
// ffprobeが出力するのはPropertyNamingStrategy.SNAKE_CASEのJSONであるため、そのように指定する
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
// JSON -> Javaオブジェクトの変換を実装
Map<String, List<FfprobeStream>> map
= mapper.readValue(ffprobeResult, new TypeReference<Map<String, List<FfprobeStream>>>() {});
// パース結果はMapで出てくるため、パラメータ指定でGetして返す
return map.getOrDefault("streams", Collections.emptyList());
}