Content of file MediaItem.java.html

<?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 :: MusicBrainz</a> &gt; <a href="../index.html" class="el_bundle">it-tidalwave-bluemarine2-model</a> &gt; <a href="index.source.html" class="el_package">it.tidalwave.bluemarine2.model</a> &gt; <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 &quot;License&quot;); 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 &quot;AS IS&quot; 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&lt;Long&gt; FILE_SIZE = Key.of(&quot;file.size&quot;, Long.class);</span>

<span class="fc" id="L73">        public static final Key&lt;Duration&gt; DURATION = Key.of(&quot;mp3.duration&quot;, Duration.class);</span>
<span class="fc" id="L74">        public static final Key&lt;Integer&gt; BIT_RATE = Key.of(&quot;mp3.bitRate&quot;, Integer.class);</span>
<span class="fc" id="L75">        public static final Key&lt;Integer&gt; SAMPLE_RATE = Key.of(&quot;mp3.sampleRate&quot;, Integer.class);</span>
<span class="fc" id="L76">        public static final Key&lt;String&gt; ARTIST = Key.of(&quot;mp3.artist&quot;, String.class);</span>
<span class="fc" id="L77">        public static final Key&lt;String&gt; COMPOSER = Key.of(&quot;mp3.composer&quot;, String.class);</span>
<span class="fc" id="L78">        public static final Key&lt;String&gt; PUBLISHER = Key.of(&quot;mp3.publisher&quot;, String.class);</span>
<span class="fc" id="L79">        public static final Key&lt;String&gt; TITLE = Key.of(&quot;mp3.title&quot;, String.class);</span>
<span class="fc" id="L80">        public static final Key&lt;Integer&gt; YEAR = Key.of(&quot;mp3.year&quot;, Integer.class);</span>
<span class="fc" id="L81">        public static final Key&lt;String&gt; ALBUM = Key.of(&quot;mp3.album&quot;, String.class);</span>
<span class="fc" id="L82">        public static final Key&lt;Integer&gt; TRACK_NUMBER = Key.of(&quot;mp3.trackNumber&quot;, Integer.class);</span>
<span class="fc" id="L83">        public static final Key&lt;Integer&gt; DISK_NUMBER = Key.of(&quot;mp3.diskNumber&quot;, Integer.class);</span>
<span class="fc" id="L84">        public static final Key&lt;Integer&gt; DISK_COUNT = Key.of(&quot;mp3.diskCount&quot;, Integer.class);</span>
<span class="fc" id="L85">        public static final Key&lt;List&lt;String&gt;&gt; COMMENT = new Key&lt;&gt;(&quot;mp3.comment&quot;) {};</span>
<span class="fc" id="L86">        public static final Key&lt;Integer&gt; BITS_PER_SAMPLE = Key.of(&quot;mp3.bitsPerSample&quot;, Integer.class);</span>
<span class="fc" id="L87">        public static final Key&lt;String&gt; FORMAT = Key.of(&quot;mp3.format&quot;, String.class);</span>
<span class="fc" id="L88">        public static final Key&lt;String&gt; ENCODING_TYPE = Key.of(&quot;mp3.encodingType&quot;, String.class);</span>
<span class="fc" id="L89">        public static final Key&lt;Integer&gt; CHANNELS = Key.of(&quot;mp3.channels&quot;, Integer.class);</span>

<span class="fc" id="L91">        public static final Key&lt;List&lt;byte[]&gt;&gt; ARTWORK = new Key&lt;&gt;(&quot;mp3.artwork&quot;) {};</span>

<span class="fc" id="L93">        public static final Key&lt;Id&gt; MBZ_TRACK_ID = Key.of(&quot;mbz.trackId&quot;, Id.class);</span>
<span class="fc" id="L94">        public static final Key&lt;Id&gt; MBZ_WORK_ID = Key.of(&quot;mbz.workId&quot;, Id.class);</span>
<span class="fc" id="L95">        public static final Key&lt;Id&gt; MBZ_DISC_ID = Key.of(&quot;mbz.discId&quot;, Id.class);</span>
<span class="fc" id="L96">        public static final Key&lt;List&lt;Id&gt;&gt; MBZ_ARTIST_ID = new Key&lt;&gt;(&quot;mbz.artistId&quot;) {};</span>

<span class="fc" id="L98">        public final Key&lt;List&lt;String&gt;&gt; ENCODER = new Key&lt;&gt;(&quot;tag.ENCODER&quot;) {}; // FIXME: key name</span>
key name
<span class="fc" id="L100"> public static final Key&lt;ITunesComment&gt; ITUNES_COMMENT = Key.of(&quot;iTunes.comment&quot;, ITunesComment.class);</span> <span class="fc" id="L101"> public static final Key&lt;Cddb&gt; CDDB = Key.of(&quot;cddb&quot;, 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="fc" id="L112"> private final String discId;</span> @Nonnull <span class="fc" id="L115"> private final int[] trackFrameOffsets;</span> <span class="fc" 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="fc" id="L129"> return String.format(&quot;1+%d+%d+%s&quot;, trackFrameOffsets.length, discLength,</span> <span class="fc" id="L130"> Arrays.toString(trackFrameOffsets).replace(&quot;, &quot;, &quot;+&quot;).replace(&quot;[&quot;, &quot;&quot;).replace(&quot;]&quot;, &quot;&quot;));</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="fc bfc" id="L157" title="All 2 branches covered."> if (Arrays.equals(this.trackFrameOffsets, other.trackFrameOffsets))</span> { <span class="fc" id="L159"> return true;</span> } <span class="fc bfc" id="L162" title="All 2 branches covered."> if (!this.sameTrackCountOf(other))</span> { <span class="fc" id="L164"> return false;</span> } <span class="fc bfc" id="L167" title="All 2 branches covered."> return this.computeDifference(other) &lt;= 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="fc bfc" id="L180" title="All 2 branches covered."> 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="fc" id="L193"> final int delta = this.trackFrameOffsets[0] - other.trackFrameOffsets[0];</span> <span class="fc" id="L194"> double acc = 0;</span> <span class="fc bfc" id="L196" title="All 2 branches covered."> for (int i = 1; i &lt; this.trackFrameOffsets.length; i++)</span> { <span class="fc" id="L198"> final double x = (this.trackFrameOffsets[i] - other.trackFrameOffsets[i] - delta)</span> / (double)other.trackFrameOffsets[i]; <span class="fc" id="L200"> acc += x * x;</span> } <span class="fc" 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> &quot;MediaItem.Metadata.ITunesComment\\(cddb1=([^,]*), cddbTrackNumber=([0-9]+)\\)&quot;); @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 + &quot;/&quot; + 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(&quot;\\+&quot;)[0])</span> <span class="fc" id="L248"> .discLength(Integer.parseInt(cddb1.split(&quot;\\+&quot;)[1]))</span> <span class="fc" id="L249"> .trackFrameOffsets(Stream.of(cddb1.split(&quot;\\+&quot;))</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&lt;ITunesComment&gt; 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 -&gt; encoders.stream().anyMatch(encoder -&gt; encoder.startsWith(&quot;iTunes&quot;))</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="fc" id="L284"> final Matcher matcher = PATTERN_TO_STRING.matcher(string);</span> <span class="pc bpc" id="L286" title="1 of 2 branches missed."> if (!matcher.matches())</span> { <span class="nc" id="L288"> throw new IllegalArgumentException(&quot;Invalid string: &quot; + string);</span> } <span class="fc" 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&lt;ITunesComment&gt; from (@Nonnull final List &lt;String&gt; comments) { <span class="fc bfc" id="L305" title="All 2 branches covered."> return comments.get(comments.size() - 2).contains(&quot;+&quot;)</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 &lt;T&gt; the type of the item * @param key the key * @return the item * **************************************************************************************************************/ @Nonnull public &lt;T&gt; Optional&lt;T&gt; get (@Nonnull Key&lt;T&gt; key); /*************************************************************************************************************** * * Extracts a metadata item (typically a collection) associated to the given key. * * @param &lt;T&gt; the type of the item * @param key the key * @return the item * **************************************************************************************************************/ @Nonnull public &lt;T&gt; T getAll (@Nonnull Key&lt;T&gt; 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&lt;?&gt; key); /*************************************************************************************************************** * * Returns all the keys contained in this instance. * * @return all the keys * **************************************************************************************************************/ @Nonnull public Set&lt;Key&lt;?&gt;&gt; getKeys(); /*************************************************************************************************************** * * Returns all the entries (key -&gt; value) contained in this instance. * * @return all the entries * **************************************************************************************************************/ @Nonnull public Set&lt;Map.Entry&lt;Key&lt;?&gt;, ?&gt;&gt; getEntries(); /*************************************************************************************************************** * * Returns a clone of this object with an additional item. * * @param &lt;T&gt; the type of the item * @param key the key * @param value the value * @return the clone * **************************************************************************************************************/ @Nonnull public &lt;T&gt; Metadata with (@Nonnull Key&lt;T&gt; key, T value); /*************************************************************************************************************** * * Returns a clone of this object with an additional optional value. * * @param &lt;T&gt; the type of the item * @param key the key * @param value the value * @return the clone * **************************************************************************************************************/ @Nonnull public &lt;T&gt; Metadata with (@Nonnull Key&lt;T&gt; key, Optional&lt;T&gt; 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&lt;Key&lt;?&gt;, Metadata&gt; 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>