<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../../jacoco-resources/report.gif" type="image/gif"/><title>MediaItem.java</title><link rel="stylesheet" href="../../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../../index.html" class="el_report">blueMarine II :: UI</a> > <a href="../index.html" class="el_bundle">it-tidalwave-bluemarine2-model</a> > <a href="index.source.html" class="el_package">it.tidalwave.bluemarine2.model</a> > <span class="el_source">MediaItem.java</span></div><h1>MediaItem.java</h1><pre class="source lang-java linenums">/*
* *********************************************************************************************************************
*
* blueMarine II: Semantic Media Centre
* http://tidalwave.it/projects/bluemarine2
*
* Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
*
* *********************************************************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* *********************************************************************************************************************
*
* git clone https://bitbucket.org/tidalwave/bluemarine2-src
* git clone https://github.com/tidalwave-it/bluemarine2-src
*
* *********************************************************************************************************************
*/
package it.tidalwave.bluemarine2.model;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import it.tidalwave.util.Id;
import it.tidalwave.util.Key;
import it.tidalwave.bluemarine2.model.role.AudioFileSupplier;
import it.tidalwave.bluemarine2.model.spi.PathAwareEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import static lombok.AccessLevel.PRIVATE;
/***********************************************************************************************************************
*
* Represents a media item. It is usually associated with one or more files on a filesystem.
*
* @stereotype Datum
*
* @author Fabrizio Giudici
*
**********************************************************************************************************************/
public interface MediaItem extends PathAwareEntity, AudioFileSupplier
{
/*******************************************************************************************************************
*
* A container of metadata objects for a {@link MediaItem}.
*
******************************************************************************************************************/
public interface Metadata
{
<span class="fc" id="L71"> public static final Key<Long> FILE_SIZE = Key.of("file.size", Long.class);</span>
<span class="fc" id="L73"> public static final Key<Duration> DURATION = Key.of("mp3.duration", Duration.class);</span>
<span class="fc" id="L74"> public static final Key<Integer> BIT_RATE = Key.of("mp3.bitRate", Integer.class);</span>
<span class="fc" id="L75"> public static final Key<Integer> SAMPLE_RATE = Key.of("mp3.sampleRate", Integer.class);</span>
<span class="fc" id="L76"> public static final Key<String> ARTIST = Key.of("mp3.artist", String.class);</span>
<span class="fc" id="L77"> public static final Key<String> COMPOSER = Key.of("mp3.composer", String.class);</span>
<span class="fc" id="L78"> public static final Key<String> PUBLISHER = Key.of("mp3.publisher", String.class);</span>
<span class="fc" id="L79"> public static final Key<String> TITLE = Key.of("mp3.title", String.class);</span>
<span class="fc" id="L80"> public static final Key<Integer> YEAR = Key.of("mp3.year", Integer.class);</span>
<span class="fc" id="L81"> public static final Key<String> ALBUM = Key.of("mp3.album", String.class);</span>
<span class="fc" id="L82"> public static final Key<Integer> TRACK_NUMBER = Key.of("mp3.trackNumber", Integer.class);</span>
<span class="fc" id="L83"> public static final Key<Integer> DISK_NUMBER = Key.of("mp3.diskNumber", Integer.class);</span>
<span class="fc" id="L84"> public static final Key<Integer> DISK_COUNT = Key.of("mp3.diskCount", Integer.class);</span>
<span class="fc" id="L85"> public static final Key<List<String>> COMMENT = new Key<>("mp3.comment") {};</span>
<span class="fc" id="L86"> public static final Key<Integer> BITS_PER_SAMPLE = Key.of("mp3.bitsPerSample", Integer.class);</span>
<span class="fc" id="L87"> public static final Key<String> FORMAT = Key.of("mp3.format", String.class);</span>
<span class="fc" id="L88"> public static final Key<String> ENCODING_TYPE = Key.of("mp3.encodingType", String.class);</span>
<span class="fc" id="L89"> public static final Key<Integer> CHANNELS = Key.of("mp3.channels", Integer.class);</span>
<span class="fc" id="L91"> public static final Key<List<byte[]>> ARTWORK = new Key<>("mp3.artwork") {};</span>
<span class="fc" id="L93"> public static final Key<Id> MBZ_TRACK_ID = Key.of("mbz.trackId", Id.class);</span>
<span class="fc" id="L94"> public static final Key<Id> MBZ_WORK_ID = Key.of("mbz.workId", Id.class);</span>
<span class="fc" id="L95"> public static final Key<Id> MBZ_DISC_ID = Key.of("mbz.discId", Id.class);</span>
<span class="fc" id="L96"> public static final Key<List<Id>> MBZ_ARTIST_ID = new Key<>("mbz.artistId") {};</span>
<span class="fc" id="L98"> public final Key<List<String>> ENCODER = new Key<>("tag.ENCODER") {}; // FIXME: key name</span>
key name
<span class="fc" id="L100"> public static final Key<ITunesComment> ITUNES_COMMENT = Key.of("iTunes.comment", ITunesComment.class);</span>
<span class="fc" id="L101"> public static final Key<Cddb> CDDB = Key.of("cddb", Cddb.class);</span>
/***************************************************************************************************************
*
* The CDDB item.
*
**************************************************************************************************************/
<span class="pc bnc" id="L108" title="All 18 branches missed."> @Immutable @AllArgsConstructor(access = PRIVATE) @Getter @Builder @ToString @EqualsAndHashCode</span>
public static class Cddb
{
@Nonnull
<span class="nc" id="L112"> private final String discId;</span>
@Nonnull
<span class="nc" id="L115"> private final int[] trackFrameOffsets;</span>
<span class="nc" id="L117"> private final int discLength;</span>
/***********************************************************************************************************
*
* Returns the TOC (Table Of Contents) of this CDDB in string form (e.g. {@code 1+3+4506+150+3400+4000})
*
* @return the TOC
*
**********************************************************************************************************/
@Nonnull
public String getToc()
{
<span class="nc" id="L129"> return String.format("1+%d+%d+%s", trackFrameOffsets.length, discLength,</span>
<span class="nc" id="L130"> Arrays.toString(trackFrameOffsets).replace(", ", "+").replace("[", "").replace("]", ""));</span>
}
/***********************************************************************************************************
*
* Returns the number of tracks in the TOC
*
* @return the number of tracks
*
**********************************************************************************************************/
@Nonnegative
public int getTrackCount()
{
<span class="nc" id="L143"> return trackFrameOffsets.length;</span>
}
/***********************************************************************************************************
*
* Returns {@code true} if this object matches the other CDDB within a given threshold.
*
* @param other the other CDDB
* @param threshold the threshold of the comparison
* @return {@code true} if this object matches
*
**********************************************************************************************************/
public boolean matches (@Nonnull final Cddb other, @Nonnegative final int threshold)
{
<span class="nc bnc" id="L157" title="All 2 branches missed."> if (Arrays.equals(this.trackFrameOffsets, other.trackFrameOffsets))</span>
{
<span class="nc" id="L159"> return true;</span>
}
<span class="nc bnc" id="L162" title="All 2 branches missed."> if (!this.sameTrackCountOf(other))</span>
{
<span class="nc" id="L164"> return false;</span>
}
<span class="nc bnc" id="L167" title="All 2 branches missed."> return this.computeDifference(other) <= threshold;</span>
}
/***********************************************************************************************************
*
* Returns {@code true} if this object contains the same number of tracks of the other CDDB
*
* @param other the other CDDB
* @return {@code true} if the number of tracks matches
*
**********************************************************************************************************/
public boolean sameTrackCountOf (@Nonnull final Cddb other)
{
<span class="nc bnc" id="L180" title="All 2 branches missed."> return this.trackFrameOffsets.length == other.trackFrameOffsets.length;</span>
}
/***********************************************************************************************************
*
* Computes the difference to another CDDB.
*
* @param other the other CDDB
* @return the difference
*
**********************************************************************************************************/
public int computeDifference (@Nonnull final Cddb other)
{
<span class="nc" id="L193"> final int delta = this.trackFrameOffsets[0] - other.trackFrameOffsets[0];</span>
<span class="nc" id="L194"> double acc = 0;</span>
<span class="nc bnc" id="L196" title="All 2 branches missed."> for (int i = 1; i < this.trackFrameOffsets.length; i++)</span>
{
<span class="nc" id="L198"> final double x = (this.trackFrameOffsets[i] - other.trackFrameOffsets[i] - delta)</span>
/ (double)other.trackFrameOffsets[i];
<span class="nc" id="L200"> acc += x * x;</span>
}
<span class="nc" id="L203"> return (int)Math.round(acc * 1E6);</span>
}
}
/***************************************************************************************************************
*
*
*
**************************************************************************************************************/
<span class="pc bnc" id="L212" title="All 22 branches missed."> @Immutable @AllArgsConstructor(access = PRIVATE) @Getter @ToString @EqualsAndHashCode</span>
public static class ITunesComment
{
<span class="fc" id="L215"> private static final Pattern PATTERN_TO_STRING = Pattern.compile(</span>
"MediaItem.Metadata.ITunesComment\\(cddb1=([^,]*), cddbTrackNumber=([0-9]+)\\)");
@Nonnull
<span class="fc" id="L219"> private final String cddb1;</span>
@Nonnull
<span class="fc" id="L222"> private final String cddbTrackNumber;</span>
/***********************************************************************************************************
*
* Returns an unique track id out of the data in this object.
*
* @return the track id
*
**********************************************************************************************************/
@Nonnull
public String getTrackId()
{
<span class="nc" id="L234"> return cddb1 + "/" + cddbTrackNumber;</span>
}
/***********************************************************************************************************
*
* Returns the same data in form of a CDDB.
*
* @return the CDDB
*
**********************************************************************************************************/
@Nonnull
public Cddb getCddb()
{
<span class="fc" id="L247"> return Cddb.builder().discId(cddb1.split("\\+")[0])</span>
<span class="fc" id="L248"> .discLength(Integer.parseInt(cddb1.split("\\+")[1]))</span>
<span class="fc" id="L249"> .trackFrameOffsets(Stream.of(cddb1.split("\\+"))</span>
<span class="fc" id="L250"> .skip(3)</span>
<span class="fc" id="L251"> .mapToInt(Integer::parseInt)</span>
<span class="fc" id="L252"> .toArray())</span>
<span class="fc" id="L253"> .build();</span>
}
/***********************************************************************************************************
*
* Factory method extracting data from a {@link Metadata} instance.
*
* @param metadata the data source
* @return the {@code ITunesComment}
*
**********************************************************************************************************/
@Nonnull
public static Optional<ITunesComment> from (@Nonnull final Metadata metadata)
{
<span class="fc" id="L267"> return metadata.get(ENCODER).flatMap(</span>
<span class="pc bpc" id="L268" title="1 of 2 branches missed."> encoders -> encoders.stream().anyMatch(encoder -> encoder.startsWith("iTunes"))</span>
<span class="fc" id="L269"> ? metadata.get(COMMENT).flatMap(ITunesComment::from)</span>
<span class="nc" id="L270"> : Optional.empty());</span>
}
/***********************************************************************************************************
*
* Factory method extracting data from a string representation.
*
* @param string the string source
* @return the {@code ITunesComment}
*
**********************************************************************************************************/
@Nonnull
public static ITunesComment fromToString (@Nonnull final String string)
{
<span class="nc" id="L284"> final Matcher matcher = PATTERN_TO_STRING.matcher(string);</span>
<span class="nc bnc" id="L286" title="All 2 branches missed."> if (!matcher.matches())</span>
{
<span class="nc" id="L288"> throw new IllegalArgumentException("Invalid string: " + string);</span>
}
<span class="nc" id="L291"> return new ITunesComment(matcher.group(1), matcher.group(2));</span>
}
/***********************************************************************************************************
*
* Factory method extracting data from a string representation as in the iTunes Comment MP3 tag.
*
* @param comments the source
* @return the {@code ITunesComment}
*
**********************************************************************************************************/
@Nonnull
private static Optional<ITunesComment> from (@Nonnull final List <String> comments)
{
<span class="fc bfc" id="L305" title="All 2 branches covered."> return comments.get(comments.size() - 2).contains("+")</span>
<span class="fc" id="L306"> ? Optional.of(new ITunesComment(comments.get(3), comments.get(4)))</span>
<span class="fc" id="L307"> : Optional.empty();</span>
}
}
/***************************************************************************************************************
*
* Extracts a single metadata item associated to the given key.
*
* @param <T> the type of the item
* @param key the key
* @return the item
*
**************************************************************************************************************/
@Nonnull
public <T> Optional<T> get (@Nonnull Key<T> key);
/***************************************************************************************************************
*
* Extracts a metadata item (typically a collection) associated to the given key.
*
* @param <T> the type of the item
* @param key the key
* @return the item
*
**************************************************************************************************************/
@Nonnull
public <T> T getAll (@Nonnull Key<T> key);
/***************************************************************************************************************
*
* Returns {@code true} if an item with the given key is present.
*
* @param key the key
* @return {@code true} if found
*
**************************************************************************************************************/
public boolean containsKey (@Nonnull Key<?> key);
/***************************************************************************************************************
*
* Returns all the keys contained in this instance.
*
* @return all the keys
*
**************************************************************************************************************/
@Nonnull
public Set<Key<?>> getKeys();
/***************************************************************************************************************
*
* Returns all the entries (key -> value) contained in this instance.
*
* @return all the entries
*
**************************************************************************************************************/
@Nonnull
public Set<Map.Entry<Key<?>, ?>> getEntries();
/***************************************************************************************************************
*
* Returns a clone of this object with an additional item.
*
* @param <T> the type of the item
* @param key the key
* @param value the value
* @return the clone
*
**************************************************************************************************************/
@Nonnull
public <T> Metadata with (@Nonnull Key<T> key, T value);
/***************************************************************************************************************
*
* Returns a clone of this object with an additional optional value.
*
* @param <T> the type of the item
* @param key the key
* @param value the value
* @return the clone
*
**************************************************************************************************************/
@Nonnull
public <T> Metadata with (@Nonnull Key<T> key, Optional<T> value);
/***************************************************************************************************************
*
* Returns a clone of this object with a fallback data source; when an item is searched and not found, before
* giving up it will be searched in the given fallback.
*
* @param fallback the fallback
* @return the clone
*
**************************************************************************************************************/
@Nonnull
public Metadata withFallback (@Nonnull Function<Key<?>, Metadata> fallback);
}
/*******************************************************************************************************************
*
* Returns the {@link Metadata} associated with this object.
*
* @return the metadata
*
******************************************************************************************************************/
@Nonnull
public Metadata getMetadata();
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.7.202105040129</span></div></body></html>