- Import brings in videos (MP4, MOV, MKV, WebM, AVI, WMV, MPEG, MTS/M2TS, FLV) on Windows, read with FFmpeg: the file as it was in the original, a frame a tenth of the way in (at most 3 s), turned the right way up, as the rendered image (what the grid, Capture window, OCR and labels see), and import.json with Duration, FrameRate, VideoCodec, Rotation, PosterTime, Audio, and the camera, date and location from the container. Elsewhere videos are skipped as "video (Windows only)". - .mcap version 4: the original's size is a u64, so a video can be any size. Versions 1-3 still read. mcap_read_without_original and mcap_write_streamed read and rewrite a file with its original left on disk; mcap_store, mcap_erase, the import index and the import itself never hold a video in memory. - Animated GIFs: Frames and Duration in import.json (gif_info). - Grid: a play mark and duration on video thumbnails, GIF on animations. Capture window: what the video is, and Play in the default player (written out to %TEMP%\mcap). - modules/ffmpeg: FFmpeg 6.0 bindings and the five DLLs used (avformat, avcodec, avutil, swscale, swresample), copied beside the executable by the build. swscale converts into a buffer of its own allocator with padded rows: an exact-sized one let its SIMD write past the end, crashing on frames of odd widths (1916, 1106 ...). - image-probe shows videos and GIF frame counts; ocr-bench reads a video's frame. - Tests: new video formats, display-matrix rotation, ISO 6709, video dates, GIF frames, and v4 serialize/parse, v3 compatibility, streamed writes, store/erase and extraction. SnagitConverter's copy of Capture_File needs syncing to read version 4 files.
89 lines
4.5 KiB
Plaintext
89 lines
4.5 KiB
Plaintext
// image-probe: what modules/Image_Decode makes of image and video files. For each FILE, its kind, EXIF and the
|
|
// decoded size; with -out FOLDER, the decoded image (turned the right way up) and its .mcap preview
|
|
// written there as PNGs, to look at.
|
|
//
|
|
// Build: jai first.jai - image_probe Run: bin/image-probe FILE... [-out FOLDER]
|
|
|
|
main :: () {
|
|
args := get_command_line_arguments();
|
|
files: [..] string;
|
|
out_folder: string;
|
|
i := 1;
|
|
while i < args.count {
|
|
if args[i] == "-out" && i + 1 < args.count { out_folder = args[i + 1]; i += 2; continue; }
|
|
array_add(*files, args[i]);
|
|
i += 1;
|
|
}
|
|
if !files.count { print("Usage: image-probe FILE... [-out FOLDER]\n"); exit(2); }
|
|
if out_folder make_directory_if_it_does_not_exist(out_folder, recursive = true);
|
|
|
|
failures := 0;
|
|
for path: files {
|
|
// A video: what video_poster finds, and its poster frame.
|
|
head: [256] u8;
|
|
head_count := 0;
|
|
file, opened := file_open(path, log_errors = false);
|
|
if opened {
|
|
_, head_count = file_read(file, head.data, head.count);
|
|
file_close(*file);
|
|
}
|
|
head_view := array_view(head, 0, head_count);
|
|
if image_kind(head_view) == .VIDEO || array_find(string.["mts", "m2ts", "ts"], to_lower_copy(path_extension(path),, temp)) {
|
|
start := current_time_monotonic();
|
|
image, info, decoded, error := video_poster(path);
|
|
print("%\n video, %\n", path, video_format_name(head_view, path));
|
|
if !decoded { print(" cannot decode: %\n", error); failures += 1; continue; }
|
|
print(" % x % as shown, % s, % fps, %, rotation %, audio %; poster at % s, in % ms\n", info.width, info.height, FF(info.duration, 2), FF(info.frame_rate, 2), info.codec, info.rotation, info.has_audio, FF(info.poster_time, 2), FF(to_float64_seconds(current_time_monotonic() - start) * 1000, 1));
|
|
print(" creationdate \"%\", creation_time \"%\", make \"%\", model \"%\", software \"%\", location \"%\"\n", info.creation_date, info.creation_time, info.make, info.model, info.software, info.location);
|
|
if out_folder {
|
|
name := path_strip_extension(path_filename(path));
|
|
stbi_write_png(temp_c_string(tprint("%/%.poster.png", out_folder, name)), image.width, image.height, 4, image.pixels, image.width * 4);
|
|
print(" wrote %/%.poster.png\n", out_folder, name);
|
|
}
|
|
image_free(*image);
|
|
reset_temporary_storage();
|
|
continue;
|
|
}
|
|
|
|
data, ok := read_entire_file(path, log_errors = false);
|
|
if !ok { print("%: cannot be read\n", path); failures += 1; continue; }
|
|
bytes := cast([] u8) data;
|
|
kind := image_kind_from_name(image_kind(bytes), path);
|
|
exif := exif_read(bytes, kind);
|
|
print("%\n kind %, % bytes\n", path, image_kind_name(kind), bytes.count);
|
|
if kind == .GIF {
|
|
frames, duration_ms := gif_info(bytes);
|
|
print(" GIF: % frames, % ms\n", frames, duration_ms);
|
|
}
|
|
if exif.found {
|
|
print(" EXIF: orientation %, date \"%\" %, make \"%\", model \"%\", software \"%\"\n", exif.orientation, exif.date_original, exif.offset_original, exif.make, exif.model, exif.software);
|
|
if exif.has_gps print(" GPS: %, %, % m\n", exif.latitude, exif.longitude, exif.altitude);
|
|
}
|
|
if kind == .HEIF || kind == .AVIF {
|
|
heif := heif_info(bytes);
|
|
print(" container: % x %, irot %, imir %, Exif item % bytes\n", heif.width, heif.height, heif.rotation, heif.mirror, heif.exif.count);
|
|
}
|
|
start := current_time_monotonic();
|
|
image, decoded, error := image_decode(bytes, kind);
|
|
if !decoded { print(" cannot decode: %\n", error); failures += 1; continue; }
|
|
print(" decoded: % x % in % ms (%)\n", image.width, image.height, FF(to_float64_seconds(current_time_monotonic() - start) * 1000, 1), ifx image.from_stb then "stb_image" else "WIC or turned");
|
|
if out_folder {
|
|
name := path_strip_extension(path_filename(path));
|
|
full := stbi_write_png(temp_c_string(tprint("%/%.png", out_folder, name)), image.width, image.height, 4, image.pixels, image.width * 4);
|
|
png, w, h, preview_ok, preview_error := image_preview_png(image, 320);
|
|
if preview_ok write_entire_file(tprint("%/%.preview.png", out_folder, name), png.data, png.count);
|
|
print(" wrote %/%.png%\n", out_folder, name, ifx preview_ok then tprint(" and a % x % preview", w, h) else tprint("; no preview: %", preview_error));
|
|
}
|
|
image_free(*image);
|
|
reset_temporary_storage();
|
|
}
|
|
exit(xx ifx failures then 1 else 0);
|
|
}
|
|
|
|
|
|
#import "Basic";
|
|
#import "String";
|
|
#import "File";
|
|
#import "Image_Decode";
|
|
#import "stb_image_write";
|