1 | /*
|
---|
2 | * Copyright 2002-2015 Drew Noakes
|
---|
3 | *
|
---|
4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
---|
5 | * you may not use this file except in compliance with the License.
|
---|
6 | * You may obtain a copy of the License at
|
---|
7 | *
|
---|
8 | * http://www.apache.org/licenses/LICENSE-2.0
|
---|
9 | *
|
---|
10 | * Unless required by applicable law or agreed to in writing, software
|
---|
11 | * distributed under the License is distributed on an "AS IS" BASIS,
|
---|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
---|
13 | * See the License for the specific language governing permissions and
|
---|
14 | * limitations under the License.
|
---|
15 | *
|
---|
16 | * More information about this project is available at:
|
---|
17 | *
|
---|
18 | * https://drewnoakes.com/code/exif/
|
---|
19 | * https://github.com/drewnoakes/metadata-extractor
|
---|
20 | */
|
---|
21 | package com.drew.metadata;
|
---|
22 |
|
---|
23 | import com.drew.lang.Rational;
|
---|
24 | import com.drew.lang.StringUtil;
|
---|
25 | import com.drew.lang.annotations.NotNull;
|
---|
26 | import com.drew.lang.annotations.Nullable;
|
---|
27 |
|
---|
28 | import java.io.UnsupportedEncodingException;
|
---|
29 | import java.lang.reflect.Array;
|
---|
30 | import java.util.ArrayList;
|
---|
31 | import java.util.Date;
|
---|
32 | import java.util.List;
|
---|
33 |
|
---|
34 | /**
|
---|
35 | * Base class for all tag descriptor classes. Implementations are responsible for
|
---|
36 | * providing the human-readable string representation of tag values stored in a directory.
|
---|
37 | * The directory is provided to the tag descriptor via its constructor.
|
---|
38 | *
|
---|
39 | * @author Drew Noakes https://drewnoakes.com
|
---|
40 | */
|
---|
41 | public class TagDescriptor<T extends Directory>
|
---|
42 | {
|
---|
43 | @NotNull
|
---|
44 | protected final T _directory;
|
---|
45 |
|
---|
46 | public TagDescriptor(@NotNull T directory)
|
---|
47 | {
|
---|
48 | _directory = directory;
|
---|
49 | }
|
---|
50 |
|
---|
51 | /**
|
---|
52 | * Returns a descriptive value of the specified tag for this image.
|
---|
53 | * Where possible, known values will be substituted here in place of the raw
|
---|
54 | * tokens actually kept in the metadata segment. If no substitution is
|
---|
55 | * available, the value provided by <code>getString(tagType)</code> will be returned.
|
---|
56 | *
|
---|
57 | * @param tagType the tag to find a description for
|
---|
58 | * @return a description of the image's value for the specified tag, or
|
---|
59 | * <code>null</code> if the tag hasn't been defined.
|
---|
60 | */
|
---|
61 | @Nullable
|
---|
62 | public String getDescription(int tagType)
|
---|
63 | {
|
---|
64 | Object object = _directory.getObject(tagType);
|
---|
65 |
|
---|
66 | if (object == null)
|
---|
67 | return null;
|
---|
68 |
|
---|
69 | // special presentation for long arrays
|
---|
70 | if (object.getClass().isArray()) {
|
---|
71 | final int length = Array.getLength(object);
|
---|
72 | if (length > 16) {
|
---|
73 | final String componentTypeName = object.getClass().getComponentType().getName();
|
---|
74 | return String.format("[%d %s%s]", length, componentTypeName, length == 1 ? "" : "s");
|
---|
75 | }
|
---|
76 | }
|
---|
77 |
|
---|
78 | // no special handling required, so use default conversion to a string
|
---|
79 | return _directory.getString(tagType);
|
---|
80 | }
|
---|
81 |
|
---|
82 | /**
|
---|
83 | * Takes a series of 4 bytes from the specified offset, and converts these to a
|
---|
84 | * well-known version number, where possible.
|
---|
85 | * <p>
|
---|
86 | * Two different formats are processed:
|
---|
87 | * <ul>
|
---|
88 | * <li>[30 32 31 30] -> 2.10</li>
|
---|
89 | * <li>[0 1 0 0] -> 1.00</li>
|
---|
90 | * </ul>
|
---|
91 | *
|
---|
92 | * @param components the four version values
|
---|
93 | * @param majorDigits the number of components to be
|
---|
94 | * @return the version as a string of form "2.10" or null if the argument cannot be converted
|
---|
95 | */
|
---|
96 | @Nullable
|
---|
97 | public static String convertBytesToVersionString(@Nullable int[] components, final int majorDigits)
|
---|
98 | {
|
---|
99 | if (components == null)
|
---|
100 | return null;
|
---|
101 | StringBuilder version = new StringBuilder();
|
---|
102 | for (int i = 0; i < 4 && i < components.length; i++) {
|
---|
103 | if (i == majorDigits)
|
---|
104 | version.append('.');
|
---|
105 | char c = (char)components[i];
|
---|
106 | if (c < '0')
|
---|
107 | c += '0';
|
---|
108 | if (i == 0 && c == '0')
|
---|
109 | continue;
|
---|
110 | version.append(c);
|
---|
111 | }
|
---|
112 | return version.toString();
|
---|
113 | }
|
---|
114 |
|
---|
115 | @Nullable
|
---|
116 | protected String getVersionBytesDescription(final int tagType, int majorDigits)
|
---|
117 | {
|
---|
118 | int[] values = _directory.getIntArray(tagType);
|
---|
119 | return values == null ? null : convertBytesToVersionString(values, majorDigits);
|
---|
120 | }
|
---|
121 |
|
---|
122 | @Nullable
|
---|
123 | protected String getIndexedDescription(final int tagType, @NotNull String... descriptions)
|
---|
124 | {
|
---|
125 | return getIndexedDescription(tagType, 0, descriptions);
|
---|
126 | }
|
---|
127 |
|
---|
128 | @Nullable
|
---|
129 | protected String getIndexedDescription(final int tagType, final int baseIndex, @NotNull String... descriptions)
|
---|
130 | {
|
---|
131 | final Integer index = _directory.getInteger(tagType);
|
---|
132 | if (index == null)
|
---|
133 | return null;
|
---|
134 | final int arrayIndex = index - baseIndex;
|
---|
135 | if (arrayIndex >= 0 && arrayIndex < descriptions.length) {
|
---|
136 | String description = descriptions[arrayIndex];
|
---|
137 | if (description != null)
|
---|
138 | return description;
|
---|
139 | }
|
---|
140 | return "Unknown (" + index + ")";
|
---|
141 | }
|
---|
142 |
|
---|
143 | @Nullable
|
---|
144 | protected String getByteLengthDescription(final int tagType)
|
---|
145 | {
|
---|
146 | byte[] bytes = _directory.getByteArray(tagType);
|
---|
147 | if (bytes == null)
|
---|
148 | return null;
|
---|
149 | return String.format("(%d byte%s)", bytes.length, bytes.length == 1 ? "" : "s");
|
---|
150 | }
|
---|
151 |
|
---|
152 | @Nullable
|
---|
153 | protected String getSimpleRational(final int tagType)
|
---|
154 | {
|
---|
155 | Rational value = _directory.getRational(tagType);
|
---|
156 | if (value == null)
|
---|
157 | return null;
|
---|
158 | return value.toSimpleString(true);
|
---|
159 | }
|
---|
160 |
|
---|
161 | @Nullable
|
---|
162 | protected String getDecimalRational(final int tagType, final int decimalPlaces)
|
---|
163 | {
|
---|
164 | Rational value = _directory.getRational(tagType);
|
---|
165 | if (value == null)
|
---|
166 | return null;
|
---|
167 | return String.format("%." + decimalPlaces + "f", value.doubleValue());
|
---|
168 | }
|
---|
169 |
|
---|
170 | @Nullable
|
---|
171 | protected String getFormattedInt(final int tagType, @NotNull final String format)
|
---|
172 | {
|
---|
173 | Integer value = _directory.getInteger(tagType);
|
---|
174 | if (value == null)
|
---|
175 | return null;
|
---|
176 | return String.format(format, value);
|
---|
177 | }
|
---|
178 |
|
---|
179 | @Nullable
|
---|
180 | protected String getFormattedFloat(final int tagType, @NotNull final String format)
|
---|
181 | {
|
---|
182 | Float value = _directory.getFloatObject(tagType);
|
---|
183 | if (value == null)
|
---|
184 | return null;
|
---|
185 | return String.format(format, value);
|
---|
186 | }
|
---|
187 |
|
---|
188 | @Nullable
|
---|
189 | protected String getFormattedString(final int tagType, @NotNull final String format)
|
---|
190 | {
|
---|
191 | String value = _directory.getString(tagType);
|
---|
192 | if (value == null)
|
---|
193 | return null;
|
---|
194 | return String.format(format, value);
|
---|
195 | }
|
---|
196 |
|
---|
197 | @Nullable
|
---|
198 | protected String getEpochTimeDescription(final int tagType)
|
---|
199 | {
|
---|
200 | // TODO have observed a byte[8] here which is likely some kind of date (ticks as long?)
|
---|
201 | Long value = _directory.getLongObject(tagType);
|
---|
202 | if (value==null)
|
---|
203 | return null;
|
---|
204 | return new Date(value).toString();
|
---|
205 | }
|
---|
206 |
|
---|
207 | /**
|
---|
208 | * LSB first. Labels may be null, a String, or a String[2] with (low label,high label) values.
|
---|
209 | */
|
---|
210 | @Nullable
|
---|
211 | protected String getBitFlagDescription(final int tagType, @NotNull final Object... labels)
|
---|
212 | {
|
---|
213 | Integer value = _directory.getInteger(tagType);
|
---|
214 |
|
---|
215 | if (value == null)
|
---|
216 | return null;
|
---|
217 |
|
---|
218 | List<String> parts = new ArrayList<String>();
|
---|
219 |
|
---|
220 | int bitIndex = 0;
|
---|
221 | while (labels.length > bitIndex) {
|
---|
222 | Object labelObj = labels[bitIndex];
|
---|
223 | if (labelObj != null) {
|
---|
224 | boolean isBitSet = (value & 1) == 1;
|
---|
225 | if (labelObj instanceof String[]) {
|
---|
226 | String[] labelPair = (String[])labelObj;
|
---|
227 | assert(labelPair.length == 2);
|
---|
228 | parts.add(labelPair[isBitSet ? 1 : 0]);
|
---|
229 | } else if (isBitSet && labelObj instanceof String) {
|
---|
230 | parts.add((String)labelObj);
|
---|
231 | }
|
---|
232 | }
|
---|
233 | value >>= 1;
|
---|
234 | bitIndex++;
|
---|
235 | }
|
---|
236 |
|
---|
237 | return StringUtil.join(parts, ", ");
|
---|
238 | }
|
---|
239 |
|
---|
240 | @Nullable
|
---|
241 | protected String get7BitStringFromBytes(final int tagType)
|
---|
242 | {
|
---|
243 | final byte[] bytes = _directory.getByteArray(tagType);
|
---|
244 |
|
---|
245 | if (bytes == null)
|
---|
246 | return null;
|
---|
247 |
|
---|
248 | int length = bytes.length;
|
---|
249 | for (int index = 0; index < bytes.length; index++) {
|
---|
250 | int i = bytes[index] & 0xFF;
|
---|
251 | if (i == 0 || i > 0x7F) {
|
---|
252 | length = index;
|
---|
253 | break;
|
---|
254 | }
|
---|
255 | }
|
---|
256 |
|
---|
257 | return new String(bytes, 0, length);
|
---|
258 | }
|
---|
259 |
|
---|
260 | @Nullable
|
---|
261 | protected String getAsciiStringFromBytes(int tag)
|
---|
262 | {
|
---|
263 | byte[] values = _directory.getByteArray(tag);
|
---|
264 |
|
---|
265 | if (values == null)
|
---|
266 | return null;
|
---|
267 |
|
---|
268 | try {
|
---|
269 | return new String(values, "ASCII").trim();
|
---|
270 | } catch (UnsupportedEncodingException e) {
|
---|
271 | return null;
|
---|
272 | }
|
---|
273 | }
|
---|
274 | }
|
---|