Project

General

Profile

Download (17.9 KB) Statistics
| Branch: | Tag: | Revision:
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.log4j.Logger;
17

    
18
import eu.etaxonomy.cdm.model.common.CdmBase;
19

    
20
public class MediaUtils {
21

    
22
    private static final Logger logger = Logger.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
  }
(12-12/16)