Skip to contentPackage: MediaItem$Metadata$4
MediaItem$Metadata$4
name | instruction | branch | complexity | line | method |
---|
{...} |
|
|
|
|
|
Coverage
1: /*
2: * *********************************************************************************************************************
3: *
4: * blueMarine II: Semantic Media Centre
5: * http://tidalwave.it/projects/bluemarine2
6: *
7: * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *********************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
12: * the License. You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
17: * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
18: * specific language governing permissions and limitations under the License.
19: *
20: * *********************************************************************************************************************
21: *
22: * git clone https://bitbucket.org/tidalwave/bluemarine2-src
23: * git clone https://github.com/tidalwave-it/bluemarine2-src
24: *
25: * *********************************************************************************************************************
26: */
27: package it.tidalwave.bluemarine2.model;
28:
29: import javax.annotation.Nonnegative;
30: import javax.annotation.Nonnull;
31: import javax.annotation.concurrent.Immutable;
32: import java.time.Duration;
33: import java.util.Arrays;
34: import java.util.List;
35: import java.util.Map;
36: import java.util.Optional;
37: import java.util.Set;
38: import java.util.function.Function;
39: import java.util.regex.Matcher;
40: import java.util.regex.Pattern;
41: import java.util.stream.Stream;
42: import it.tidalwave.util.Id;
43: import it.tidalwave.util.Key;
44: import it.tidalwave.bluemarine2.model.role.AudioFileSupplier;
45: import it.tidalwave.bluemarine2.model.spi.PathAwareEntity;
46: import lombok.AllArgsConstructor;
47: import lombok.Builder;
48: import lombok.EqualsAndHashCode;
49: import lombok.Getter;
50: import lombok.ToString;
51: import static lombok.AccessLevel.PRIVATE;
52:
53: /***********************************************************************************************************************
54: *
55: * Represents a media item. It is usually associated with one or more files on a filesystem.
56: *
57: * @stereotype Datum
58: *
59: * @author Fabrizio Giudici
60: *
61: **********************************************************************************************************************/
62: public interface MediaItem extends PathAwareEntity, AudioFileSupplier
63: {
64: /*******************************************************************************************************************
65: *
66: * A container of metadata objects for a {@link MediaItem}.
67: *
68: ******************************************************************************************************************/
69: public interface Metadata
70: {
71: public static final Key<Long> FILE_SIZE = Key.of("file.size", Long.class);
72:
73: public static final Key<Duration> DURATION = Key.of("mp3.duration", Duration.class);
74: public static final Key<Integer> BIT_RATE = Key.of("mp3.bitRate", Integer.class);
75: public static final Key<Integer> SAMPLE_RATE = Key.of("mp3.sampleRate", Integer.class);
76: public static final Key<String> ARTIST = Key.of("mp3.artist", String.class);
77: public static final Key<String> COMPOSER = Key.of("mp3.composer", String.class);
78: public static final Key<String> PUBLISHER = Key.of("mp3.publisher", String.class);
79: public static final Key<String> TITLE = Key.of("mp3.title", String.class);
80: public static final Key<Integer> YEAR = Key.of("mp3.year", Integer.class);
81: public static final Key<String> ALBUM = Key.of("mp3.album", String.class);
82: public static final Key<Integer> TRACK_NUMBER = Key.of("mp3.trackNumber", Integer.class);
83: public static final Key<Integer> DISK_NUMBER = Key.of("mp3.diskNumber", Integer.class);
84: public static final Key<Integer> DISK_COUNT = Key.of("mp3.diskCount", Integer.class);
85: public static final Key<List<String>> COMMENT = new Key<>("mp3.comment") {};
86: public static final Key<Integer> BITS_PER_SAMPLE = Key.of("mp3.bitsPerSample", Integer.class);
87: public static final Key<String> FORMAT = Key.of("mp3.format", String.class);
88: public static final Key<String> ENCODING_TYPE = Key.of("mp3.encodingType", String.class);
89: public static final Key<Integer> CHANNELS = Key.of("mp3.channels", Integer.class);
90:
91: public static final Key<List<byte[]>> ARTWORK = new Key<>("mp3.artwork") {};
92:
93: public static final Key<Id> MBZ_TRACK_ID = Key.of("mbz.trackId", Id.class);
94: public static final Key<Id> MBZ_WORK_ID = Key.of("mbz.workId", Id.class);
95: public static final Key<Id> MBZ_DISC_ID = Key.of("mbz.discId", Id.class);
96: public static final Key<List<Id>> MBZ_ARTIST_ID = new Key<>("mbz.artistId") {};
97:
98: public final Key<List<String>> ENCODER = new Key<>("tag.ENCODER") {}; // FIXME: key name
99:
100: public static final Key<ITunesComment> ITUNES_COMMENT = Key.of("iTunes.comment", ITunesComment.class);
101: public static final Key<Cddb> CDDB = Key.of("cddb", Cddb.class);
102:
103: /***************************************************************************************************************
104: *
105: * The CDDB item.
106: *
107: **************************************************************************************************************/
108: @Immutable @AllArgsConstructor(access = PRIVATE) @Getter @Builder @ToString @EqualsAndHashCode
109: public static class Cddb
110: {
111: @Nonnull
112: private final String discId;
113:
114: @Nonnull
115: private final int[] trackFrameOffsets;
116:
117: private final int discLength;
118:
119: /***********************************************************************************************************
120: *
121: * Returns the TOC (Table Of Contents) of this CDDB in string form (e.g. {@code 1+3+4506+150+3400+4000})
122: *
123: * @return the TOC
124: *
125: **********************************************************************************************************/
126: @Nonnull
127: public String getToc()
128: {
129: return String.format("1+%d+%d+%s", trackFrameOffsets.length, discLength,
130: Arrays.toString(trackFrameOffsets).replace(", ", "+").replace("[", "").replace("]", ""));
131: }
132:
133: /***********************************************************************************************************
134: *
135: * Returns the number of tracks in the TOC
136: *
137: * @return the number of tracks
138: *
139: **********************************************************************************************************/
140: @Nonnegative
141: public int getTrackCount()
142: {
143: return trackFrameOffsets.length;
144: }
145:
146: /***********************************************************************************************************
147: *
148: * Returns {@code true} if this object matches the other CDDB within a given threshold.
149: *
150: * @param other the other CDDB
151: * @param threshold the threshold of the comparison
152: * @return {@code true} if this object matches
153: *
154: **********************************************************************************************************/
155: public boolean matches (@Nonnull final Cddb other, @Nonnegative final int threshold)
156: {
157: if (Arrays.equals(this.trackFrameOffsets, other.trackFrameOffsets))
158: {
159: return true;
160: }
161:
162: if (!this.sameTrackCountOf(other))
163: {
164: return false;
165: }
166:
167: return this.computeDifference(other) <= threshold;
168: }
169:
170: /***********************************************************************************************************
171: *
172: * Returns {@code true} if this object contains the same number of tracks of the other CDDB
173: *
174: * @param other the other CDDB
175: * @return {@code true} if the number of tracks matches
176: *
177: **********************************************************************************************************/
178: public boolean sameTrackCountOf (@Nonnull final Cddb other)
179: {
180: return this.trackFrameOffsets.length == other.trackFrameOffsets.length;
181: }
182:
183: /***********************************************************************************************************
184: *
185: * Computes the difference to another CDDB.
186: *
187: * @param other the other CDDB
188: * @return the difference
189: *
190: **********************************************************************************************************/
191: public int computeDifference (@Nonnull final Cddb other)
192: {
193: final int delta = this.trackFrameOffsets[0] - other.trackFrameOffsets[0];
194: double acc = 0;
195:
196: for (int i = 1; i < this.trackFrameOffsets.length; i++)
197: {
198: final double x = (this.trackFrameOffsets[i] - other.trackFrameOffsets[i] - delta)
199: / (double)other.trackFrameOffsets[i];
200: acc += x * x;
201: }
202:
203: return (int)Math.round(acc * 1E6);
204: }
205: }
206:
207: /***************************************************************************************************************
208: *
209: *
210: *
211: **************************************************************************************************************/
212: @Immutable @AllArgsConstructor(access = PRIVATE) @Getter @ToString @EqualsAndHashCode
213: public static class ITunesComment
214: {
215: private static final Pattern PATTERN_TO_STRING = Pattern.compile(
216: "MediaItem.Metadata.ITunesComment\\(cddb1=([^,]*), cddbTrackNumber=([0-9]+)\\)");
217:
218: @Nonnull
219: private final String cddb1;
220:
221: @Nonnull
222: private final String cddbTrackNumber;
223:
224: /***********************************************************************************************************
225: *
226: * Returns an unique track id out of the data in this object.
227: *
228: * @return the track id
229: *
230: **********************************************************************************************************/
231: @Nonnull
232: public String getTrackId()
233: {
234: return cddb1 + "/" + cddbTrackNumber;
235: }
236:
237: /***********************************************************************************************************
238: *
239: * Returns the same data in form of a CDDB.
240: *
241: * @return the CDDB
242: *
243: **********************************************************************************************************/
244: @Nonnull
245: public Cddb getCddb()
246: {
247: return Cddb.builder().discId(cddb1.split("\\+")[0])
248: .discLength(Integer.parseInt(cddb1.split("\\+")[1]))
249: .trackFrameOffsets(Stream.of(cddb1.split("\\+"))
250: .skip(3)
251: .mapToInt(Integer::parseInt)
252: .toArray())
253: .build();
254: }
255:
256: /***********************************************************************************************************
257: *
258: * Factory method extracting data from a {@link Metadata} instance.
259: *
260: * @param metadata the data source
261: * @return the {@code ITunesComment}
262: *
263: **********************************************************************************************************/
264: @Nonnull
265: public static Optional<ITunesComment> from (@Nonnull final Metadata metadata)
266: {
267: return metadata.get(ENCODER).flatMap(
268: encoders -> encoders.stream().anyMatch(encoder -> encoder.startsWith("iTunes"))
269: ? metadata.get(COMMENT).flatMap(ITunesComment::from)
270: : Optional.empty());
271: }
272:
273: /***********************************************************************************************************
274: *
275: * Factory method extracting data from a string representation.
276: *
277: * @param string the string source
278: * @return the {@code ITunesComment}
279: *
280: **********************************************************************************************************/
281: @Nonnull
282: public static ITunesComment fromToString (@Nonnull final String string)
283: {
284: final Matcher matcher = PATTERN_TO_STRING.matcher(string);
285:
286: if (!matcher.matches())
287: {
288: throw new IllegalArgumentException("Invalid string: " + string);
289: }
290:
291: return new ITunesComment(matcher.group(1), matcher.group(2));
292: }
293:
294: /***********************************************************************************************************
295: *
296: * Factory method extracting data from a string representation as in the iTunes Comment MP3 tag.
297: *
298: * @param comments the source
299: * @return the {@code ITunesComment}
300: *
301: **********************************************************************************************************/
302: @Nonnull
303: private static Optional<ITunesComment> from (@Nonnull final List <String> comments)
304: {
305: return comments.get(comments.size() - 2).contains("+")
306: ? Optional.of(new ITunesComment(comments.get(3), comments.get(4)))
307: : Optional.empty();
308: }
309: }
310:
311: /***************************************************************************************************************
312: *
313: * Extracts a single metadata item associated to the given key.
314: *
315: * @param <T> the type of the item
316: * @param key the key
317: * @return the item
318: *
319: **************************************************************************************************************/
320: @Nonnull
321: public <T> Optional<T> get (@Nonnull Key<T> key);
322:
323: /***************************************************************************************************************
324: *
325: * Extracts a metadata item (typically a collection) associated to the given key.
326: *
327: * @param <T> the type of the item
328: * @param key the key
329: * @return the item
330: *
331: **************************************************************************************************************/
332: @Nonnull
333: public <T> T getAll (@Nonnull Key<T> key);
334:
335: /***************************************************************************************************************
336: *
337: * Returns {@code true} if an item with the given key is present.
338: *
339: * @param key the key
340: * @return {@code true} if found
341: *
342: **************************************************************************************************************/
343: public boolean containsKey (@Nonnull Key<?> key);
344:
345: /***************************************************************************************************************
346: *
347: * Returns all the keys contained in this instance.
348: *
349: * @return all the keys
350: *
351: **************************************************************************************************************/
352: @Nonnull
353: public Set<Key<?>> getKeys();
354:
355: /***************************************************************************************************************
356: *
357: * Returns all the entries (key -> value) contained in this instance.
358: *
359: * @return all the entries
360: *
361: **************************************************************************************************************/
362: @Nonnull
363: public Set<Map.Entry<Key<?>, ?>> getEntries();
364:
365: /***************************************************************************************************************
366: *
367: * Returns a clone of this object with an additional item.
368: *
369: * @param <T> the type of the item
370: * @param key the key
371: * @param value the value
372: * @return the clone
373: *
374: **************************************************************************************************************/
375: @Nonnull
376: public <T> Metadata with (@Nonnull Key<T> key, T value);
377:
378: /***************************************************************************************************************
379: *
380: * Returns a clone of this object with an additional optional value.
381: *
382: * @param <T> the type of the item
383: * @param key the key
384: * @param value the value
385: * @return the clone
386: *
387: **************************************************************************************************************/
388: @Nonnull
389: public <T> Metadata with (@Nonnull Key<T> key, Optional<T> value);
390:
391: /***************************************************************************************************************
392: *
393: * Returns a clone of this object with a fallback data source; when an item is searched and not found, before
394: * giving up it will be searched in the given fallback.
395: *
396: * @param fallback the fallback
397: * @return the clone
398: *
399: **************************************************************************************************************/
400: @Nonnull
401: public Metadata withFallback (@Nonnull Function<Key<?>, Metadata> fallback);
402: }
403:
404: /*******************************************************************************************************************
405: *
406: * Returns the {@link Metadata} associated with this object.
407: *
408: * @return the metadata
409: *
410: ******************************************************************************************************************/
411: @Nonnull
412: public Metadata getMetadata();
413: }