/* * Copyright 2013-2014 Odysseus Software GmbH * * 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. */ package org.musicmount.builder.impl; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; import org.musicmount.builder.model.Album; import org.musicmount.builder.model.AlbumArtist; import org.musicmount.builder.model.Disc; import org.musicmount.builder.model.Library; import org.musicmount.builder.model.Track; import org.musicmount.builder.model.TrackArtist; public class LibraryParser { static final Logger LOGGER = Logger.getLogger(LibraryParser.class.getName()); private static String trimToNonEmptyStringOrNull(String s) { if (s != null) { s = s.trim(); if (s.isEmpty()) { s = null; } } return s; } private final boolean useTrackGrouping; public LibraryParser(boolean useTrackGrouping) { this.useTrackGrouping = useTrackGrouping; } void sortTracks(Library library) { /* * sort tracks by disc number, track number, title, artist */ Comparator<Track> comparator = new Comparator<Track>() { <T extends Comparable<T>> int compareNullLast(Comparable<T> o1, T o2) { if (o1 != o2) { if (o1 == null) { return +1; } else if (o2 == null) { return -1; } else { int result = o1.compareTo(o2); if (result != 0) { return result; } } } return 0; } @Override public int compare(Track o1, Track o2) { int result = compareNullLast(o1.getDiscNumber(), o2.getDiscNumber()); if (result != 0) { return result; } result = compareNullLast(o1.getTrackNumber(), o2.getTrackNumber()); if (result != 0) { return result; } result = compareNullLast(o1.getTitle(), o2.getTitle()); if (result != 0) { return result; } return compareNullLast(o1.getArtist().getTitle(), o2.getArtist().getTitle()); } }; for (Album album : library.getAlbums()) { for (Disc disc : album.getDiscs().values()) { Collections.sort(disc.getTracks(), comparator); } } for (Album album : library.getAlbums()) { Collections.sort(album.getTracks(), comparator); } } private TrackArtist uniqueTrackArtist(Album album) { TrackArtist artist = album.getTracks().get(0).getArtist(); if (artist != null) { for (Track track : album.getTracks()) { if (track.getArtist() != artist) { return null; } } } return artist; } public final Library parse(Iterable<Asset> assets) { Library library = new Library(); // add "various artists" with id 0 library.getAlbumArtists().put(null, new AlbumArtist(0, null)); // add "unknown artist" with id 0 library.getTrackArtists().put(null, new TrackArtist(0, null)); // parse assets into library for (Asset asset : assets) { parse(library, asset); } /* * distribute compilations without album artist into "various artists" and * "unique artist" albums (which are moved to the corresponding album artist). */ AlbumArtist variousArtists = library.getAlbumArtists().get(null); Iterator<Album> variousArtistsAlbumIterator = variousArtists.getAlbums().values().iterator(); while (variousArtistsAlbumIterator.hasNext()) { Album album = variousArtistsAlbumIterator.next(); TrackArtist uniqueTrackArtist = uniqueTrackArtist(album); if (uniqueTrackArtist != null && uniqueTrackArtist.getTitle() != null) { // get album artist AlbumArtist albumArtist = library.getAlbumArtists().get(uniqueTrackArtist.getTitle()); if (albumArtist == null) { albumArtist = new AlbumArtist(library.getAlbumArtists().size(), uniqueTrackArtist.getTitle()); library.getAlbumArtists().put(uniqueTrackArtist.getTitle(), albumArtist); } Album targetAlbum = albumArtist.getAlbums().get(album.getTitle()); if (targetAlbum != null) { // merge album tracks into existing album for (Disc sourceDisc : album.getDiscs().values()) { Disc targetDisc = targetAlbum.getDiscs().get(sourceDisc.getDiscNumber()); if (targetDisc == null) { targetAlbum.getDiscs().put(sourceDisc.getDiscNumber(), sourceDisc); } else { targetDisc.getTracks().addAll(sourceDisc.getTracks()); } } uniqueTrackArtist.getAlbums().remove(album); } else { // add whole album to albumArtist albumArtist.getAlbums().put(album.getTitle(), album); album.setArtist(albumArtist); } // remove album from variousArtists variousArtistsAlbumIterator.remove(); } } // remove empty "various artists" if (library.getAlbumArtists().get(null).getAlbums().isEmpty()) { library.getAlbumArtists().remove(null); } // remove empty "unknown artist" if (library.getTrackArtists().get(null).getAlbums().isEmpty()) { library.getTrackArtists().remove(null); } // sort tracks sortTracks(library); return library; } private String trackName(Asset asset) { String trackName = trimToNonEmptyStringOrNull(asset.getName()); if (useTrackGrouping && trackName != null) { String trackGrouping = trimToNonEmptyStringOrNull(asset.getGrouping()); if (trackGrouping != null && trackName.startsWith(trackGrouping)) { String title = trackName.substring(trackGrouping.length()); if (title.length() > 0) { if (Character.isAlphabetic(title.charAt(0)) || Character.isDigit(title.charAt(0))) { return title; } for (int start = 1; start < title.length(); start++) { if (Character.isAlphabetic(title.charAt(start)) || Character.isDigit(title.charAt(start))) { return title.substring(start); } } } } } return trackName; } void parse(Library library, Asset asset) { String trackName = trackName(asset); String albumName = trimToNonEmptyStringOrNull(asset.getAlbum()); String trackArtistName = trimToNonEmptyStringOrNull(asset.getArtist()); String albumArtistName = trimToNonEmptyStringOrNull(asset.getAlbumArtist()); if (albumArtistName == null && !asset.isCompilation()) { // derive missing album artist for non-compilations albumArtistName = trackArtistName; } if (trackArtistName == null && albumArtistName != null) { // derive missing artist from album artist if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Will use album-artist for missing artist in file: " + asset.getResource().getPath().toAbsolutePath()); } trackArtistName = albumArtistName; } if (trackName == null || albumName == null && trackArtistName == null) { // unusable LOGGER.info("Will skip poorly tagged asset file: " + asset.getResource().getPath().toAbsolutePath()); return; } /* * determine album artist */ AlbumArtist albumArtist = library.getAlbumArtists().get(albumArtistName); if (albumArtist == null) { albumArtist = new AlbumArtist(library.getAlbumArtists().size(), albumArtistName); library.getAlbumArtists().put(albumArtistName, albumArtist); } /* * determine album */ Album album = albumArtist.getAlbums().get(albumName); if (album == null) { album = new Album(albumName); album.setArtist(albumArtist); albumArtist.getAlbums().put(albumName, album); library.getAlbums().add(album); } /* * determine track artist */ TrackArtist trackArtist = library.getTrackArtists().get(trackArtistName); if (trackArtist == null) { trackArtist = new TrackArtist(library.getTrackArtists().size(), trackArtistName); library.getTrackArtists().put(trackArtistName, trackArtist); } trackArtist.getAlbums().add(album); /* * create track */ Track track = new Track( trackName, asset.getResource(), asset.isArtworkAvailable(), asset.isCompilation(), trimToNonEmptyStringOrNull(asset.getComposer()), asset.getDiscNumber(), asset.getDuration(), trimToNonEmptyStringOrNull(asset.getGenre()), trimToNonEmptyStringOrNull(asset.getGrouping()), asset.getTrackNumber(), asset.getYear() ); library.getTracks().add(track); album.getTracks().add(track); track.setAlbum(album); track.setArtist(trackArtist); /* * determine disc */ Integer discKey = track.getDiscNumber(); if (discKey == null) { discKey = Integer.valueOf(0); } Disc disc = album.getDiscs().get(discKey); if (disc == null) { disc = new Disc(discKey.intValue()); album.getDiscs().put(discKey, disc); } disc.getTracks().add(track); } }