Revision 6df5928c
Added by Andreas Müller over 1 year ago
cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/media/MediaUtils.java | ||
---|---|---|
1 |
package eu.etaxonomy.cdm.model.media; |
|
2 |
|
|
3 |
import java.awt.Dimension; |
|
4 |
import java.util.ArrayList; |
|
5 |
import java.util.HashMap; |
|
6 |
import java.util.LinkedHashMap; |
|
7 |
import java.util.LinkedHashSet; |
|
8 |
import java.util.List; |
|
9 |
import java.util.Map; |
|
10 |
import java.util.Set; |
|
11 |
import java.util.SortedMap; |
|
12 |
import java.util.TreeMap; |
|
13 |
import java.util.regex.Pattern; |
|
14 |
import java.util.stream.Collectors; |
|
15 |
|
|
16 |
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger; |
|
17 |
|
|
18 |
import eu.etaxonomy.cdm.model.common.CdmBase; |
|
19 |
|
|
20 |
public class MediaUtils { |
|
21 |
|
|
22 |
private static final Logger logger = LogManager.getLogger(MediaUtils.class); |
|
23 |
|
|
24 |
|
|
25 |
public static MediaRepresentation findBestMatchingRepresentation(Media media, |
|
26 |
Class<? extends MediaRepresentationPart> representationPartType, Integer size, Integer height, |
|
27 |
Integer widthOrDuration, String[] mimeTypes, MissingValueStrategy missingValStrategy){ |
|
28 |
|
|
29 |
// find best matching representations of each media |
|
30 |
Set<MediaRepresentation> representations = media.getRepresentations(); |
|
31 |
return findBestMatchingRepresentation(representations, representationPartType, size, height, widthOrDuration, |
|
32 |
mimeTypes, missingValStrategy); |
|
33 |
} |
|
34 |
|
|
35 |
public static MediaRepresentation findBestMatchingRepresentation( |
|
36 |
Set<MediaRepresentation> representations, Class<? extends MediaRepresentationPart> representationPartType, Integer size, |
|
37 |
Integer height, Integer widthOrDuration, String[] mimeTypes, |
|
38 |
MissingValueStrategy missingValStrategy) { |
|
39 |
|
|
40 |
SortedMap<Long, MediaRepresentation> prefRepresentations |
|
41 |
= filterAndOrderMediaRepresentations(representations, representationPartType, mimeTypes, |
|
42 |
size, widthOrDuration, height, missingValStrategy); |
|
43 |
if(prefRepresentations.size() > 0){ |
|
44 |
MediaRepresentation prefOne = prefRepresentations.get(prefRepresentations.firstKey()); |
|
45 |
return prefOne; |
|
46 |
} |
|
47 |
return null; |
|
48 |
} |
|
49 |
|
|
50 |
/** |
|
51 |
* Return the first {@link MediaRepresentationPart} found for the given {@link Media} |
|
52 |
* or <code>null</code> otherwise. |
|
53 |
* @param media the media which is searched for the first part |
|
54 |
* @return the first part found or <code>null</code> |
|
55 |
*/ |
|
56 |
public static MediaRepresentationPart getFirstMediaRepresentationPart(Media media){ |
|
57 |
if(media==null){ |
|
58 |
return null; |
|
59 |
} |
|
60 |
MediaRepresentationPart mediaRepresentationPart = null; |
|
61 |
Set<MediaRepresentation> representations = media.getRepresentations(); |
|
62 |
if(representations!=null && representations.size()>0){ |
|
63 |
MediaRepresentation mediaRepresentation = representations.iterator().next(); |
|
64 |
List<MediaRepresentationPart> parts = mediaRepresentation.getParts(); |
|
65 |
if(parts!=null && parts.size()>0){ |
|
66 |
mediaRepresentationPart = parts.iterator().next(); |
|
67 |
} |
|
68 |
} |
|
69 |
return mediaRepresentationPart; |
|
70 |
} |
|
71 |
|
|
72 |
/** |
|
73 |
* Creates one single {@link MediaRepresentationPart} for the given {@link Media} |
|
74 |
* if it does not already exists. Otherwise the first part found is returned.<br> |
|
75 |
* @param media the media for which the representation part should be created |
|
76 |
* @return the first or newly created representation part |
|
77 |
*/ |
|
78 |
public static MediaRepresentationPart initFirstMediaRepresentationPart(Media media, boolean isImage) { |
|
79 |
MediaRepresentationPart mediaRepresentationPart = getFirstMediaRepresentationPart(media); |
|
80 |
if(mediaRepresentationPart==null){ |
|
81 |
Set<MediaRepresentation> representations = media.getRepresentations(); |
|
82 |
if(representations!=null && representations.size()>0){ |
|
83 |
MediaRepresentation mediaRepresentation = representations.iterator().next(); |
|
84 |
if(isImage){ |
|
85 |
mediaRepresentationPart = ImageFile.NewInstance(null, null); |
|
86 |
} |
|
87 |
else{ |
|
88 |
mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null); |
|
89 |
} |
|
90 |
mediaRepresentation.addRepresentationPart(mediaRepresentationPart); |
|
91 |
} |
|
92 |
else{ |
|
93 |
if(isImage){ |
|
94 |
mediaRepresentationPart = ImageFile.NewInstance(null, null); |
|
95 |
} |
|
96 |
else{ |
|
97 |
mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null); |
|
98 |
} |
|
99 |
|
|
100 |
MediaRepresentation mediaRepresentation = MediaRepresentation.NewInstance(); |
|
101 |
mediaRepresentation.addRepresentationPart(mediaRepresentationPart); |
|
102 |
media.addRepresentation(mediaRepresentation); |
|
103 |
} |
|
104 |
} |
|
105 |
return mediaRepresentationPart; |
|
106 |
} |
|
107 |
|
|
108 |
/** |
|
109 |
* Filters the given List of Media by the supplied filter parameters <code>representationPartType</code>, |
|
110 |
* <code>mimeTypes</code>, <code>widthOrDuration</code>, <code>height</code>, <code>size</code>. |
|
111 |
* Only best matching MediaRepresentation remains attached to the Media entities. |
|
112 |
* A Media entity may be completely omitted in the resulting list if {@link #filterAndOrderMediaRepresentations(Set, Class, String[], Integer, Integer, Integer)} |
|
113 |
* is not returning any matching representation. This can be the case if a <code>representationPartType</code> is supplied. |
|
114 |
* |
|
115 |
* @param mediaList |
|
116 |
* @param representationPartType any subclass of {@link MediaRepresentationPart} |
|
117 |
* @param mimeTypes |
|
118 |
* @param widthOrDuration |
|
119 |
* @param height |
|
120 |
* @param size |
|
121 |
* @return |
|
122 |
*/ |
|
123 |
public static Map<Media, MediaRepresentation> findPreferredMedia(List<Media> mediaList, |
|
124 |
Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypes, Integer widthOrDuration, |
|
125 |
Integer height, Integer size, MissingValueStrategy missingValStrat) { |
|
126 |
|
|
127 |
if(mimeTypes != null) { |
|
128 |
for(int i=0; i<mimeTypes.length; i++){ |
|
129 |
mimeTypes[i] = mimeTypes[i].replace(':', '/'); |
|
130 |
} |
|
131 |
} |
|
132 |
|
|
133 |
Map<Media, MediaRepresentation> returnMediaList; |
|
134 |
if(mediaList != null){ |
|
135 |
returnMediaList = new LinkedHashMap<>(mediaList.size()); |
|
136 |
for(Media media : mediaList){ |
|
137 |
|
|
138 |
Set<MediaRepresentation> candidateRepresentations = new LinkedHashSet<>(); |
|
139 |
candidateRepresentations.addAll(media.getRepresentations()); |
|
140 |
|
|
141 |
SortedMap<Long, MediaRepresentation> prefRepresentations |
|
142 |
= filterAndOrderMediaRepresentations(candidateRepresentations, representationPartType, |
|
143 |
mimeTypes, size, widthOrDuration, height, missingValStrat); |
|
144 |
|
|
145 |
if(prefRepresentations.size() > 0){ |
|
146 |
// Media.representations is a set |
|
147 |
// so it cannot retain the sorting which has been found by filterAndOrderMediaRepresentations() |
|
148 |
// thus we take first one and remove all other representations |
|
149 |
returnMediaList.put(media, prefRepresentations.get(prefRepresentations.firstKey())); |
|
150 |
} |
|
151 |
|
|
152 |
} |
|
153 |
} |
|
154 |
else{ |
|
155 |
returnMediaList = new HashMap<>(); |
|
156 |
} |
|
157 |
return returnMediaList; |
|
158 |
} |
|
159 |
|
|
160 |
/** |
|
161 |
* @see also cdm-dataportal: cdm-api.module#cdm_preferred_media_representations() |
|
162 |
* |
|
163 |
* @param mediaRepresentations |
|
164 |
* @param representationPartType |
|
165 |
* @param mimeTypeRegexes |
|
166 |
* @param size |
|
167 |
* Applies to all {@link MediaRepresentationPart}s (value = <code>null</code> means ignore, for maximum size use {@link Integer#MAX_VALUE}) |
|
168 |
* @param widthOrDuration |
|
169 |
* Applied to {@link ImageFile#getWidth()}, or {@link {@link MovieFile#getDuration()}, |
|
170 |
* or {@link {@link AudioFile#getDuration()} (value = <code>null</code> means ignore, |
|
171 |
* for maximum use {@link Integer#MAX_VALUE}) |
|
172 |
* @param height |
|
173 |
* The height is only applied to {@link ImageFile}s (value = <code>null</code> means ignore, |
|
174 |
* for maximum height use {@link Integer#MAX_VALUE}) |
|
175 |
* @return |
|
176 |
*/ |
|
177 |
public static SortedMap<Long, MediaRepresentation> filterAndOrderMediaRepresentations( |
|
178 |
Set<MediaRepresentation> mediaRepresentations, |
|
179 |
Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypeRegexes, |
|
180 |
Integer size, Integer widthOrDuration, Integer height, |
|
181 |
MissingValueStrategy missingValStrat) { |
|
182 |
|
|
183 |
SortedMap<Long, MediaRepresentation> prefRepr = new TreeMap<>(); |
|
184 |
|
|
185 |
Dimension preferredImageDimensions = dimensionsFilter(widthOrDuration, height, null); |
|
186 |
long preferredExpansion = expanse(preferredImageDimensions); |
|
187 |
logger.debug("preferredExpansion: " + preferredExpansion); |
|
188 |
|
|
189 |
mimeTypeRegexes = (mimeTypeRegexes == null ? new String[]{".*"} : mimeTypeRegexes); |
|
190 |
|
|
191 |
for (String mimeTypeRegex : mimeTypeRegexes) { |
|
192 |
// getRepresentationByMimeType |
|
193 |
Pattern mimeTypePattern = Pattern.compile(mimeTypeRegex); |
|
194 |
int representationCnt = 0; |
|
195 |
for (MediaRepresentation representation : mediaRepresentations) { |
|
196 |
|
|
197 |
List<MediaRepresentationPart> matchingParts = new ArrayList<>(); |
|
198 |
|
|
199 |
|
|
200 |
// check MIME type |
|
201 |
boolean isMimeTypeMatch = representation.getMimeType() == null |
|
202 |
|| mimeTypePattern.matcher(representation.getMimeType()).matches(); |
|
203 |
if(logger.isDebugEnabled()){ |
|
204 |
logger.debug("isMimeTypeMatch: " + Boolean.valueOf(isMimeTypeMatch).toString()); |
|
205 |
} |
|
206 |
|
|
207 |
long dimensionsDeltaAllParts = 0; |
|
208 |
|
|
209 |
//first the size is used for comparison |
|
210 |
for (MediaRepresentationPart part : representation.getParts()) { |
|
211 |
|
|
212 |
// check representationPartType |
|
213 |
boolean isRepresentationPartTypeMatch = representationPartType == null |
|
214 |
|| part.getClass().isAssignableFrom(representationPartType); |
|
215 |
if(logger.isDebugEnabled()){ |
|
216 |
logger.debug("isRepresentationPartTypeMatch: " + Boolean.valueOf(isRepresentationPartTypeMatch).toString()); |
|
217 |
} |
|
218 |
|
|
219 |
if ( !(isRepresentationPartTypeMatch && isMimeTypeMatch) ) { |
|
220 |
continue; |
|
221 |
} |
|
222 |
|
|
223 |
if(logger.isDebugEnabled()){ |
|
224 |
logger.debug(part + " matches"); |
|
225 |
} |
|
226 |
matchingParts.add(part); |
|
227 |
|
|
228 |
Integer sizeOfPart = part.getSize(); |
|
229 |
if(isUndefined(sizeOfPart)){ |
|
230 |
sizeOfPart = missingValStrat.applyTo(sizeOfPart); |
|
231 |
} |
|
232 |
if (size != null && sizeOfPart != null){ |
|
233 |
int distance = sizeOfPart - size; |
|
234 |
if (distance < 0) { |
|
235 |
distance *= -1; |
|
236 |
} |
|
237 |
dimensionsDeltaAllParts += distance; |
|
238 |
|
|
239 |
} |
|
240 |
|
|
241 |
//if height and width/duration is defined, add this information, too |
|
242 |
if (preferredImageDimensions != null || widthOrDuration != null){ |
|
243 |
long expansionDelta = 0; |
|
244 |
if (part.isInstanceOf(ImageFile.class)) { |
|
245 |
if (preferredImageDimensions != null){ |
|
246 |
ImageFile image = CdmBase.deproxy(part, ImageFile.class); |
|
247 |
Dimension imageDimension = dimensionsFilter(image.getWidth(), image.getHeight(), missingValStrat); |
|
248 |
if (imageDimension != null){ |
|
249 |
expansionDelta = Math.abs(expanse(imageDimension) - preferredExpansion); |
|
250 |
} |
|
251 |
if(logger.isDebugEnabled()){ |
|
252 |
if(logger.isDebugEnabled()){ |
|
253 |
logger.debug("part [" + part.getUri() + "; " + imageDimension + "] : preferredImageDimensions= " + preferredImageDimensions + ", size= " + size+ " >>" + expansionDelta ); |
|
254 |
} |
|
255 |
} |
|
256 |
} |
|
257 |
} |
|
258 |
else if (part.isInstanceOf(MovieFile.class)){ |
|
259 |
MovieFile movie = CdmBase.deproxy(part, MovieFile.class); |
|
260 |
Integer durationOfMovie = movie.getDuration(); |
|
261 |
if(isUndefined(durationOfMovie)){ |
|
262 |
durationOfMovie = null; // convert potential 0 to null! |
|
263 |
} |
|
264 |
durationOfMovie = missingValStrat.applyTo(durationOfMovie); |
|
265 |
if(widthOrDuration != null){ |
|
266 |
expansionDelta = durationOfMovie - widthOrDuration; |
|
267 |
} |
|
268 |
if(logger.isDebugEnabled()){ |
|
269 |
logger.debug("part MovieFile[" + part.getUri() + "; duration=" + movie.getDuration() + "-> " + durationOfMovie + "] : preferrdDuration= " + widthOrDuration + ", size= " + size+ " >>" + expansionDelta ); |
|
270 |
} |
|
271 |
} else if (part.isInstanceOf(AudioFile.class)){ |
|
272 |
AudioFile audio = CdmBase.deproxy(part, AudioFile.class); |
|
273 |
Integer durationOfAudio = audio.getDuration(); |
|
274 |
if(isUndefined(durationOfAudio)){ |
|
275 |
durationOfAudio = null; // convert potential 0 to null! |
|
276 |
} |
|
277 |
durationOfAudio = missingValStrat.applyTo(durationOfAudio); |
|
278 |
if(widthOrDuration != null) { |
|
279 |
expansionDelta = durationOfAudio - widthOrDuration; |
|
280 |
} |
|
281 |
if(logger.isDebugEnabled()){ |
|
282 |
logger.debug("part AudioFile[" + part.getUri() + "; duration=" + audio.getDuration() + "-> " + durationOfAudio + "] : preferrdDuration= " + widthOrDuration + ", size= " + size + " >>" + expansionDelta ); |
|
283 |
} |
|
284 |
} |
|
285 |
// the expansionDelta is summed up since the parts together for the whole |
|
286 |
// which is bigger than only a part. By simply summing up images splitted |
|
287 |
// into parts have too much weight compared to the single image but since |
|
288 |
// parts are not used at all this is currently not a problem |
|
289 |
dimensionsDeltaAllParts += expansionDelta; |
|
290 |
|
|
291 |
} |
|
292 |
} // loop parts |
|
293 |
logger.debug("matchingParts.size():" + matchingParts.size()); |
|
294 |
if(matchingParts.size() > 0 ){ |
|
295 |
representation.getParts().clear(); |
|
296 |
representation.getParts().addAll(matchingParts); |
|
297 |
prefRepr.put((dimensionsDeltaAllParts + representationCnt++), representation); |
|
298 |
} |
|
299 |
} // loop representations |
|
300 |
} // loop mime types |
|
301 |
if(logger.isDebugEnabled()){ |
|
302 |
String text = prefRepr.keySet().stream() |
|
303 |
.map(key -> key + ": " + prefRepr.get(key).getParts().get(0).getUri().toString()) |
|
304 |
.collect(Collectors.joining(", ", "{", "}")); |
|
305 |
logger.debug("resulting representations: " + text); |
|
306 |
} |
|
307 |
|
|
308 |
|
|
309 |
return prefRepr; |
|
310 |
|
|
311 |
} |
|
312 |
|
|
313 |
/** |
|
314 |
* @param imageDimension |
|
315 |
* @return |
|
316 |
*/ |
|
317 |
static long expanse(Dimension imageDimension) { |
|
318 |
if(imageDimension != null){ |
|
319 |
return (long)imageDimension.height * (long)imageDimension.width; |
|
320 |
} else { |
|
321 |
return -1; |
|
322 |
} |
|
323 |
} |
|
324 |
|
|
325 |
/** |
|
326 |
* @param widthOrDuration |
|
327 |
* @param height |
|
328 |
* @param mvs Will be applied when both, width and height, are <code>null</code> |
|
329 |
*/ |
|
330 |
static Dimension dimensionsFilter(Integer width, Integer height, MissingValueStrategy mvs) { |
|
331 |
Dimension imageDimensions = null; |
|
332 |
if(!isUndefined(height) || !isUndefined(width)){ |
|
333 |
imageDimensions = new Dimension(); |
|
334 |
if (!isUndefined(height) && isUndefined(width)){ |
|
335 |
imageDimensions.setSize(1, height); |
|
336 |
} else if(isUndefined(height) && !isUndefined(width)) { |
|
337 |
imageDimensions.setSize(width, 1); // --> height will be respected and width is ignored |
|
338 |
} else { |
|
339 |
imageDimensions.setSize(width, height); |
|
340 |
} |
|
341 |
} else { |
|
342 |
// both, width and height, are undefined |
|
343 |
|
|
344 |
if(mvs != null){ |
|
345 |
// set both values to null so that the MissingValueStrategy can be applied |
|
346 |
// the MissingValueStrategy only get effective when the supplied value is NULL |
|
347 |
width = null; |
|
348 |
height = null; |
|
349 |
imageDimensions = new Dimension(mvs.applyTo(width), mvs.applyTo(height)); |
|
350 |
} |
|
351 |
} |
|
352 |
return imageDimensions; |
|
353 |
} |
|
354 |
|
|
355 |
static private boolean isUndefined(Integer val) { |
|
356 |
return val == null || val == 0; |
|
357 |
} |
|
358 |
|
|
359 |
/** |
|
360 |
* Strategies for replacing <code>null</code> values with a numeric value. |
|
361 |
* |
|
362 |
* @author a.kohlbecker |
|
363 |
*/ |
|
364 |
public enum MissingValueStrategy { |
|
365 |
/** |
|
366 |
* replace <code>null</code> by {@link Integer#MAX_VALUE} |
|
367 |
*/ |
|
368 |
MAX(Integer.MAX_VALUE), |
|
369 |
/** |
|
370 |
* replace <code>null</code> by <code>0</code> |
|
371 |
*/ |
|
372 |
ZERO(0); |
|
373 |
|
|
374 |
private Integer defaultValue; |
|
375 |
|
|
376 |
MissingValueStrategy(Integer defaultValue){ |
|
377 |
this.defaultValue = defaultValue; |
|
378 |
} |
|
379 |
|
|
380 |
public Integer applyTo(Integer val){ |
|
381 |
if(val == null){ |
|
382 |
return defaultValue; |
|
383 |
} else { |
|
384 |
return val; |
|
385 |
} |
|
386 |
} |
|
387 |
} |
|
388 |
} |
|
1 |
package eu.etaxonomy.cdm.model.media; |
|
2 |
|
|
3 |
import java.awt.Dimension; |
|
4 |
import java.util.ArrayList; |
|
5 |
import java.util.HashMap; |
|
6 |
import java.util.LinkedHashMap; |
|
7 |
import java.util.LinkedHashSet; |
|
8 |
import java.util.List; |
|
9 |
import java.util.Map; |
|
10 |
import java.util.Set; |
|
11 |
import java.util.SortedMap; |
|
12 |
import java.util.TreeMap; |
|
13 |
import java.util.regex.Pattern; |
|
14 |
import java.util.stream.Collectors; |
|
15 |
|
|
16 |
import org.apache.logging.log4j.LogManager; |
|
17 |
import org.apache.logging.log4j.Logger; |
|
18 |
|
|
19 |
import eu.etaxonomy.cdm.model.common.CdmBase; |
|
20 |
|
|
21 |
public class MediaUtils { |
|
22 |
|
|
23 |
private static final Logger logger = LogManager.getLogger(); |
|
24 |
|
|
25 |
|
|
26 |
public static MediaRepresentation findBestMatchingRepresentation(Media media, |
|
27 |
Class<? extends MediaRepresentationPart> representationPartType, Integer size, Integer height, |
|
28 |
Integer widthOrDuration, String[] mimeTypes, MissingValueStrategy missingValStrategy){ |
|
29 |
|
|
30 |
// find best matching representations of each media |
|
31 |
Set<MediaRepresentation> representations = media.getRepresentations(); |
|
32 |
return findBestMatchingRepresentation(representations, representationPartType, size, height, widthOrDuration, |
|
33 |
mimeTypes, missingValStrategy); |
|
34 |
} |
|
35 |
|
|
36 |
public static MediaRepresentation findBestMatchingRepresentation( |
|
37 |
Set<MediaRepresentation> representations, Class<? extends MediaRepresentationPart> representationPartType, Integer size, |
|
38 |
Integer height, Integer widthOrDuration, String[] mimeTypes, |
|
39 |
MissingValueStrategy missingValStrategy) { |
|
40 |
|
|
41 |
SortedMap<Long, MediaRepresentation> prefRepresentations |
|
42 |
= filterAndOrderMediaRepresentations(representations, representationPartType, mimeTypes, |
|
43 |
size, widthOrDuration, height, missingValStrategy); |
|
44 |
if(prefRepresentations.size() > 0){ |
|
45 |
MediaRepresentation prefOne = prefRepresentations.get(prefRepresentations.firstKey()); |
|
46 |
return prefOne; |
|
47 |
} |
|
48 |
return null; |
|
49 |
} |
|
50 |
|
|
51 |
/** |
|
52 |
* Return the first {@link MediaRepresentationPart} found for the given {@link Media} |
|
53 |
* or <code>null</code> otherwise. |
|
54 |
* @param media the media which is searched for the first part |
|
55 |
* @return the first part found or <code>null</code> |
|
56 |
*/ |
|
57 |
public static MediaRepresentationPart getFirstMediaRepresentationPart(Media media){ |
|
58 |
if(media==null){ |
|
59 |
return null; |
|
60 |
} |
|
61 |
MediaRepresentationPart mediaRepresentationPart = null; |
|
62 |
Set<MediaRepresentation> representations = media.getRepresentations(); |
|
63 |
if(representations!=null && representations.size()>0){ |
|
64 |
MediaRepresentation mediaRepresentation = representations.iterator().next(); |
|
65 |
List<MediaRepresentationPart> parts = mediaRepresentation.getParts(); |
|
66 |
if(parts!=null && parts.size()>0){ |
|
67 |
mediaRepresentationPart = parts.iterator().next(); |
|
68 |
} |
|
69 |
} |
|
70 |
return mediaRepresentationPart; |
|
71 |
} |
|
72 |
|
|
73 |
/** |
|
74 |
* Creates one single {@link MediaRepresentationPart} for the given {@link Media} |
|
75 |
* if it does not already exists. Otherwise the first part found is returned.<br> |
|
76 |
* @param media the media for which the representation part should be created |
|
77 |
* @return the first or newly created representation part |
|
78 |
*/ |
|
79 |
public static MediaRepresentationPart initFirstMediaRepresentationPart(Media media, boolean isImage) { |
|
80 |
MediaRepresentationPart mediaRepresentationPart = getFirstMediaRepresentationPart(media); |
|
81 |
if(mediaRepresentationPart==null){ |
|
82 |
Set<MediaRepresentation> representations = media.getRepresentations(); |
|
83 |
if(representations!=null && representations.size()>0){ |
|
84 |
MediaRepresentation mediaRepresentation = representations.iterator().next(); |
|
85 |
if(isImage){ |
|
86 |
mediaRepresentationPart = ImageFile.NewInstance(null, null); |
|
87 |
} |
|
88 |
else{ |
|
89 |
mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null); |
|
90 |
} |
|
91 |
mediaRepresentation.addRepresentationPart(mediaRepresentationPart); |
|
92 |
} |
|
93 |
else{ |
|
94 |
if(isImage){ |
|
95 |
mediaRepresentationPart = ImageFile.NewInstance(null, null); |
|
96 |
} |
|
97 |
else{ |
|
98 |
mediaRepresentationPart = MediaRepresentationPart.NewInstance(null, null); |
|
99 |
} |
|
100 |
|
|
101 |
MediaRepresentation mediaRepresentation = MediaRepresentation.NewInstance(); |
|
102 |
mediaRepresentation.addRepresentationPart(mediaRepresentationPart); |
|
103 |
media.addRepresentation(mediaRepresentation); |
|
104 |
} |
|
105 |
} |
|
106 |
return mediaRepresentationPart; |
|
107 |
} |
|
108 |
|
|
109 |
/** |
|
110 |
* Filters the given List of Media by the supplied filter parameters <code>representationPartType</code>, |
|
111 |
* <code>mimeTypes</code>, <code>widthOrDuration</code>, <code>height</code>, <code>size</code>. |
|
112 |
* Only best matching MediaRepresentation remains attached to the Media entities. |
|
113 |
* A Media entity may be completely omitted in the resulting list if {@link #filterAndOrderMediaRepresentations(Set, Class, String[], Integer, Integer, Integer)} |
|
114 |
* is not returning any matching representation. This can be the case if a <code>representationPartType</code> is supplied. |
|
115 |
* |
|
116 |
* @param mediaList |
|
117 |
* @param representationPartType any subclass of {@link MediaRepresentationPart} |
|
118 |
* @param mimeTypes |
|
119 |
* @param widthOrDuration |
|
120 |
* @param height |
|
121 |
* @param size |
|
122 |
* @return |
|
123 |
*/ |
|
124 |
public static Map<Media, MediaRepresentation> findPreferredMedia(List<Media> mediaList, |
|
125 |
Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypes, Integer widthOrDuration, |
|
126 |
Integer height, Integer size, MissingValueStrategy missingValStrat) { |
|
127 |
|
|
128 |
if(mimeTypes != null) { |
|
129 |
for(int i=0; i<mimeTypes.length; i++){ |
|
130 |
mimeTypes[i] = mimeTypes[i].replace(':', '/'); |
|
131 |
} |
|
132 |
} |
|
133 |
|
|
134 |
Map<Media, MediaRepresentation> returnMediaList; |
|
135 |
if(mediaList != null){ |
|
136 |
returnMediaList = new LinkedHashMap<>(mediaList.size()); |
|
137 |
for(Media media : mediaList){ |
|
138 |
|
|
139 |
Set<MediaRepresentation> candidateRepresentations = new LinkedHashSet<>(); |
|
140 |
candidateRepresentations.addAll(media.getRepresentations()); |
|
141 |
|
|
142 |
SortedMap<Long, MediaRepresentation> prefRepresentations |
|
143 |
= filterAndOrderMediaRepresentations(candidateRepresentations, representationPartType, |
|
144 |
mimeTypes, size, widthOrDuration, height, missingValStrat); |
|
145 |
|
|
146 |
if(prefRepresentations.size() > 0){ |
|
147 |
// Media.representations is a set |
|
148 |
// so it cannot retain the sorting which has been found by filterAndOrderMediaRepresentations() |
|
149 |
// thus we take first one and remove all other representations |
|
150 |
returnMediaList.put(media, prefRepresentations.get(prefRepresentations.firstKey())); |
|
151 |
} |
|
152 |
|
|
153 |
} |
|
154 |
} |
|
155 |
else{ |
|
156 |
returnMediaList = new HashMap<>(); |
|
157 |
} |
|
158 |
return returnMediaList; |
|
159 |
} |
|
160 |
|
|
161 |
/** |
|
162 |
* @see also cdm-dataportal: cdm-api.module#cdm_preferred_media_representations() |
|
163 |
* |
|
164 |
* @param mediaRepresentations |
|
165 |
* @param representationPartType |
|
166 |
* @param mimeTypeRegexes |
|
167 |
* @param size |
|
168 |
* Applies to all {@link MediaRepresentationPart}s (value = <code>null</code> means ignore, for maximum size use {@link Integer#MAX_VALUE}) |
|
169 |
* @param widthOrDuration |
|
170 |
* Applied to {@link ImageFile#getWidth()}, or {@link {@link MovieFile#getDuration()}, |
|
171 |
* or {@link {@link AudioFile#getDuration()} (value = <code>null</code> means ignore, |
|
172 |
* for maximum use {@link Integer#MAX_VALUE}) |
|
173 |
* @param height |
|
174 |
* The height is only applied to {@link ImageFile}s (value = <code>null</code> means ignore, |
|
175 |
* for maximum height use {@link Integer#MAX_VALUE}) |
|
176 |
* @return |
|
177 |
*/ |
|
178 |
public static SortedMap<Long, MediaRepresentation> filterAndOrderMediaRepresentations( |
|
179 |
Set<MediaRepresentation> mediaRepresentations, |
|
180 |
Class<? extends MediaRepresentationPart> representationPartType, String[] mimeTypeRegexes, |
|
181 |
Integer size, Integer widthOrDuration, Integer height, |
|
182 |
MissingValueStrategy missingValStrat) { |
|
183 |
|
|
184 |
SortedMap<Long, MediaRepresentation> prefRepr = new TreeMap<>(); |
|
185 |
|
|
186 |
Dimension preferredImageDimensions = dimensionsFilter(widthOrDuration, height, null); |
|
187 |
long preferredExpansion = expanse(preferredImageDimensions); |
|
188 |
logger.debug("preferredExpansion: " + preferredExpansion); |
|
189 |
|
|
190 |
mimeTypeRegexes = (mimeTypeRegexes == null ? new String[]{".*"} : mimeTypeRegexes); |
|
191 |
|
|
192 |
for (String mimeTypeRegex : mimeTypeRegexes) { |
|
193 |
// getRepresentationByMimeType |
|
194 |
Pattern mimeTypePattern = Pattern.compile(mimeTypeRegex); |
|
195 |
int representationCnt = 0; |
|
196 |
for (MediaRepresentation representation : mediaRepresentations) { |
|
197 |
|
|
198 |
List<MediaRepresentationPart> matchingParts = new ArrayList<>(); |
|
199 |
|
|
200 |
|
|
201 |
// check MIME type |
|
202 |
boolean isMimeTypeMatch = representation.getMimeType() == null |
|
203 |
|| mimeTypePattern.matcher(representation.getMimeType()).matches(); |
|
204 |
if(logger.isDebugEnabled()){ |
|
205 |
logger.debug("isMimeTypeMatch: " + Boolean.valueOf(isMimeTypeMatch).toString()); |
|
206 |
} |
|
207 |
|
|
208 |
long dimensionsDeltaAllParts = 0; |
|
209 |
|
|
210 |
//first the size is used for comparison |
|
211 |
for (MediaRepresentationPart part : representation.getParts()) { |
|
212 |
|
|
213 |
// check representationPartType |
|
214 |
boolean isRepresentationPartTypeMatch = representationPartType == null |
|
215 |
|| part.getClass().isAssignableFrom(representationPartType); |
|
216 |
if(logger.isDebugEnabled()){ |
|
217 |
logger.debug("isRepresentationPartTypeMatch: " + Boolean.valueOf(isRepresentationPartTypeMatch).toString()); |
|
218 |
} |
|
219 |
|
|
220 |
if ( !(isRepresentationPartTypeMatch && isMimeTypeMatch) ) { |
|
221 |
continue; |
|
222 |
} |
|
223 |
|
|
224 |
if(logger.isDebugEnabled()){ |
|
225 |
logger.debug(part + " matches"); |
|
226 |
} |
|
227 |
matchingParts.add(part); |
|
228 |
|
|
229 |
Integer sizeOfPart = part.getSize(); |
|
230 |
if(isUndefined(sizeOfPart)){ |
|
231 |
sizeOfPart = missingValStrat.applyTo(sizeOfPart); |
|
232 |
} |
|
233 |
if (size != null && sizeOfPart != null){ |
|
234 |
int distance = sizeOfPart - size; |
|
235 |
if (distance < 0) { |
|
236 |
distance *= -1; |
|
237 |
} |
|
238 |
dimensionsDeltaAllParts += distance; |
|
239 |
|
|
240 |
} |
|
241 |
|
|
242 |
//if height and width/duration is defined, add this information, too |
|
243 |
if (preferredImageDimensions != null || widthOrDuration != null){ |
|
244 |
long expansionDelta = 0; |
|
245 |
if (part.isInstanceOf(ImageFile.class)) { |
|
246 |
if (preferredImageDimensions != null){ |
|
247 |
ImageFile image = CdmBase.deproxy(part, ImageFile.class); |
|
248 |
Dimension imageDimension = dimensionsFilter(image.getWidth(), image.getHeight(), missingValStrat); |
|
249 |
if (imageDimension != null){ |
|
250 |
expansionDelta = Math.abs(expanse(imageDimension) - preferredExpansion); |
|
251 |
} |
|
252 |
if(logger.isDebugEnabled()){ |
|
253 |
if(logger.isDebugEnabled()){ |
|
254 |
logger.debug("part [" + part.getUri() + "; " + imageDimension + "] : preferredImageDimensions= " + preferredImageDimensions + ", size= " + size+ " >>" + expansionDelta ); |
|
255 |
} |
|
256 |
} |
|
257 |
} |
|
258 |
} |
|
259 |
else if (part.isInstanceOf(MovieFile.class)){ |
|
260 |
MovieFile movie = CdmBase.deproxy(part, MovieFile.class); |
|
261 |
Integer durationOfMovie = movie.getDuration(); |
|
262 |
if(isUndefined(durationOfMovie)){ |
|
263 |
durationOfMovie = null; // convert potential 0 to null! |
|
264 |
} |
|
265 |
durationOfMovie = missingValStrat.applyTo(durationOfMovie); |
|
266 |
if(widthOrDuration != null){ |
|
267 |
expansionDelta = durationOfMovie - widthOrDuration; |
|
268 |
} |
|
269 |
if(logger.isDebugEnabled()){ |
|
270 |
logger.debug("part MovieFile[" + part.getUri() + "; duration=" + movie.getDuration() + "-> " + durationOfMovie + "] : preferrdDuration= " + widthOrDuration + ", size= " + size+ " >>" + expansionDelta ); |
|
271 |
} |
|
272 |
} else if (part.isInstanceOf(AudioFile.class)){ |
|
273 |
AudioFile audio = CdmBase.deproxy(part, AudioFile.class); |
|
274 |
Integer durationOfAudio = audio.getDuration(); |
|
275 |
if(isUndefined(durationOfAudio)){ |
|
276 |
durationOfAudio = null; // convert potential 0 to null! |
|
277 |
} |
|
278 |
durationOfAudio = missingValStrat.applyTo(durationOfAudio); |
|
279 |
if(widthOrDuration != null) { |
|
280 |
expansionDelta = durationOfAudio - widthOrDuration; |
|
281 |
} |
|
282 |
if(logger.isDebugEnabled()){ |
|
283 |
logger.debug("part AudioFile[" + part.getUri() + "; duration=" + audio.getDuration() + "-> " + durationOfAudio + "] : preferrdDuration= " + widthOrDuration + ", size= " + size + " >>" + expansionDelta ); |
|
284 |
} |
|
285 |
} |
|
286 |
// the expansionDelta is summed up since the parts together for the whole |
|
287 |
// which is bigger than only a part. By simply summing up images splitted |
|
288 |
// into parts have too much weight compared to the single image but since |
|
289 |
// parts are not used at all this is currently not a problem |
|
290 |
dimensionsDeltaAllParts += expansionDelta; |
|
291 |
|
|
292 |
} |
|
293 |
} // loop parts |
|
294 |
logger.debug("matchingParts.size():" + matchingParts.size()); |
|
295 |
if(matchingParts.size() > 0 ){ |
|
296 |
representation.getParts().clear(); |
|
297 |
representation.getParts().addAll(matchingParts); |
|
298 |
prefRepr.put((dimensionsDeltaAllParts + representationCnt++), representation); |
|
299 |
} |
|
300 |
} // loop representations |
|
301 |
} // loop mime types |
|
302 |
if(logger.isDebugEnabled()){ |
|
303 |
String text = prefRepr.keySet().stream() |
|
304 |
.map(key -> key + ": " + prefRepr.get(key).getParts().get(0).getUri().toString()) |
|
305 |
.collect(Collectors.joining(", ", "{", "}")); |
|
306 |
logger.debug("resulting representations: " + text); |
|
307 |
} |
|
308 |
|
|
309 |
return prefRepr; |
|
310 |
} |
|
311 |
|
|
312 |
static long expanse(Dimension imageDimension) { |
|
313 |
if(imageDimension != null){ |
|
314 |
return (long)imageDimension.height * (long)imageDimension.width; |
|
315 |
} else { |
|
316 |
return -1; |
|
317 |
} |
|
318 |
} |
|
319 |
|
|
320 |
/** |
|
321 |
* @param widthOrDuration |
|
322 |
* @param height |
|
323 |
* @param mvs Will be applied when both, width and height, are <code>null</code> |
|
324 |
*/ |
|
325 |
static Dimension dimensionsFilter(Integer width, Integer height, MissingValueStrategy mvs) { |
|
326 |
Dimension imageDimensions = null; |
|
327 |
if(!isUndefined(height) || !isUndefined(width)){ |
|
328 |
imageDimensions = new Dimension(); |
|
329 |
if (!isUndefined(height) && isUndefined(width)){ |
|
330 |
imageDimensions.setSize(1, height); |
|
331 |
} else if(isUndefined(height) && !isUndefined(width)) { |
|
332 |
imageDimensions.setSize(width, 1); // --> height will be respected and width is ignored |
|
333 |
} else { |
|
334 |
imageDimensions.setSize(width, height); |
|
335 |
} |
|
336 |
} else { |
|
337 |
// both, width and height, are undefined |
|
338 |
|
|
339 |
if(mvs != null){ |
|
340 |
// set both values to null so that the MissingValueStrategy can be applied |
|
341 |
// the MissingValueStrategy only get effective when the supplied value is NULL |
|
342 |
width = null; |
|
343 |
height = null; |
|
344 |
imageDimensions = new Dimension(mvs.applyTo(width), mvs.applyTo(height)); |
|
345 |
} |
|
346 |
} |
|
347 |
return imageDimensions; |
|
348 |
} |
|
349 |
|
|
350 |
static private boolean isUndefined(Integer val) { |
|
351 |
return val == null || val == 0; |
|
352 |
} |
|
353 |
|
|
354 |
/** |
|
355 |
* Strategies for replacing <code>null</code> values with a numeric value. |
|
356 |
* |
|
357 |
* @author a.kohlbecker |
|
358 |
*/ |
|
359 |
public enum MissingValueStrategy { |
|
360 |
/** |
|
361 |
* replace <code>null</code> by {@link Integer#MAX_VALUE} |
|
362 |
*/ |
|
363 |
MAX(Integer.MAX_VALUE), |
|
364 |
/** |
|
365 |
* replace <code>null</code> by <code>0</code> |
|
366 |
*/ |
|
367 |
ZERO(0); |
|
368 |
|
|
369 |
private Integer defaultValue; |
|
370 |
|
|
371 |
MissingValueStrategy(Integer defaultValue){ |
|
372 |
this.defaultValue = defaultValue; |
|
373 |
} |
|
374 |
|
|
375 |
public Integer applyTo(Integer val){ |
|
376 |
if(val == null){ |
|
377 |
return defaultValue; |
|
378 |
} else { |
|
379 |
return val; |
|
380 |
} |
|
381 |
} |
|
382 |
} |
|
383 |
} |
Also available in: Unified diff
cleanup