1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
/* Copyright 2016 Software Freedom Conservancy Inc.
*
* This software is licensed under the GNU LGPL (version 2.1 or later).
* See the COPYING file in this distribution.
*/
public class DetectedPhotoInformation {
public PhotoFileFormat file_format = PhotoFileFormat.UNKNOWN;
public PhotoMetadata? metadata = null;
public string? md5 = null;
public string? exif_md5 = null;
public string? thumbnail_md5 = null;
public string? format_name = null;
public Dimensions image_dim = Dimensions();
public Gdk.Colorspace colorspace = Gdk.Colorspace.RGB;
public int channels = 0;
public int bits_per_channel = 0;
}
//
// A PhotoFileSniffer is expected to examine the supplied file as efficiently as humanly possible
// to detect (a) if it is of a file format supported by the particular sniffer, and (b) fill out
// a DetectedPhotoInformation record and return it to the caller.
//
// The PhotoFileSniffer is not expected to cache information. It should return a fresh
// DetectedPhotoInformation record each time.
//
// PhotoFileSniffer must be thread-safe. Like PhotoFileAdapters, it is not expected to guarantee
// atomicity with respect to the filesystem.
//
public abstract class PhotoFileSniffer {
public enum Options {
GET_ALL = 0x00000000,
NO_MD5 = 0x00000001
}
protected File file;
protected Options options;
protected bool calc_md5;
protected PhotoFileSniffer(File file, Options options) {
this.file = file;
this.options = options;
calc_md5 = (options & Options.NO_MD5) == 0;
}
public abstract DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error;
protected static bool is_supported_bmff_with_variants(File file, string[] variants) throws Error {
FileInputStream instream = file.read(null);
// Skip the first four bytes
if (instream.skip(4) != 4) {
return false;
}
// The next four bytes need to be ftyp
var buf = new uint8[4];
if (instream.read(buf, null) != 4) {
return false;
}
if (Memory.cmp("ftyp".data, buf, 4) != 0) {
return false;
}
if (instream.read(buf, null) != 4) {
return false;
}
buf += '\0';
return (string)buf in variants;
}
}
//
// PhotoFileInterrogator
//
// A PhotoFileInterrogator is merely an aggregator of PhotoFileSniffers. It will create sniffers
// for each supported PhotoFileFormat and see if they recognize the file.
//
// The PhotoFileInterrogator is not thread-safe.
//
public class PhotoFileInterrogator {
private File file;
private PhotoFileSniffer.Options options;
private DetectedPhotoInformation? detected = null;
private bool is_photo_corrupted = false;
public PhotoFileInterrogator(File file,
PhotoFileSniffer.Options options = PhotoFileSniffer.Options.GET_ALL) {
this.file = file;
this.options = options;
}
// This should only be called after interrogate(). Will return null every time, otherwise.
// If called after interrogate and returns null, that indicates the file is not an image file.
public DetectedPhotoInformation? get_detected_photo_information() {
return detected;
}
// Call after interrogate().
public bool get_is_photo_corrupted() {
return is_photo_corrupted;
}
public void interrogate() throws Error {
foreach (PhotoFileFormat file_format in PhotoFileFormat.get_supported()) {
PhotoFileSniffer sniffer = file_format.create_sniffer(file, options);
bool is_corrupted;
detected = sniffer.sniff(out is_corrupted);
if (detected != null && !is_corrupted) {
assert(detected.file_format == file_format);
break;
} else if (is_corrupted) {
message("Sniffing halted for %s: potentially corrupted image file", file.get_path());
is_photo_corrupted = true;
detected = null;
break;
}
}
}
}
|