summaryrefslogtreecommitdiff
path: root/src/photos/PhotoFileSniffer.vala
blob: 635892025d4cfc61df85fed2c7a944a5c3cdde67 (plain)
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;
            }
        }
    }
}