/* You may freely copy, distribute, modify and use this class as long as the original author attribution remains intact. See message below. Copyright (C) 2001-2006 Christian Pesch. All Rights Reserved. */ package slash.metamusic.mp3.tools; import slash.metamusic.coverdb.*; import slash.metamusic.lyricsdb.LyricsDBClient; import slash.metamusic.mp3.MP3File; import slash.metamusic.trm.TRM; import slash.metamusic.util.DiscIndexHelper; import slash.metamusic.util.ImageResizer; import java.io.File; import java.io.IOException; import java.util.*; import java.util.logging.Logger; import static slash.metamusic.util.ContributorHelper.*; /** * A class to add * <ul> * <li>MusicBrainz id,</li> * <li>cover image,</li> * <li>lyrics,</li> * <li>publisher information and</li> * <li>compilation name</li> * </ul> * to files. * * @author Christian Pesch * @version $Id: MP3Extender.java 475 2005-01-14 17:31:47Z cpesch $ */ public class MP3Extender extends BaseMP3Modifier { /** * Logging output */ protected static final Logger log = Logger.getLogger(MP3Extender.class.getName()); private static final int RESIZE_PIXELS = 600; private CoverDBClient coverClient = new CoverDBClient(); private LyricsDBClient lyricsClient = new LyricsDBClient(); private boolean addCoverToFolder = false, addCover = true, addLyrics = true, addMetaData = true; public boolean isAddCoverToFolder() { return addCoverToFolder; } public void setAddCoverToFolder(boolean addCoverToFolder) { this.addCoverToFolder = addCoverToFolder; } public boolean isAddCover() { return addCover; } public void setAddCover(boolean addCover) { this.addCover = addCover; } public boolean isAddLyrics() { return addLyrics; } public void setAddLyrics(boolean addLyrics) { this.addLyrics = addLyrics; } public boolean isAddMetaData() { return addMetaData; } public void setAddMetaData(boolean addMetaData) { this.addMetaData = addMetaData; } public void setCoverDirectoryName(String coverDirectoryName) { coverClient.setCoverDirectoryName(coverDirectoryName); } public void setLyricsDirectoryName(String lyricsDirectoryName) { lyricsClient.setLyricsDirectoryName(lyricsDirectoryName); } /** * Extend the given file, i.e. add * <ul> * <li>MusicBrainz id,</li> * <li>cover image,</li> * <li>lyrics,</li> * <li>publisher information and</li> * <li>compilation name</li> * </ul> * to the file. * * @param file the {@link File} to operate on * @throws IOException if the file cannot be read */ public void extend(File file) throws IOException { MP3File mp3 = MP3File.readValidFile(file); if (mp3 == null) { throw new IOException("Invalid MP3 file " + file.getAbsolutePath()); } extend(mp3); } /** * Extend the given MP3 file, i.e. add * <ul> * <li>MusicBrainz id,</li> * <li>cover image,</li> * <li>lyrics,</li> * <li>publisher information and</li> * <li>compilation name</li> * </ul> * to the MP3 file. * * @param file the {@link MP3File} to operate on * @throws IOException if the file cannot be read */ public void extend(MP3File file) throws IOException { boolean haveToWrite = extendTags(file); if (haveToWrite) { write(file); log.info("Extended " + file.getFile().getAbsolutePath()); } } public boolean extendTags(MP3File file) throws IOException { boolean addedMusicBrainzId = isAddMetaData() && addMusicBrainzId(file); boolean addedAttachedPicture = isAddCover() && addCover(file); boolean addedLyrics = isAddLyrics() && addLyrics(file); boolean addedPublisher = isAddMetaData() && addPublisher(file); boolean addedCompilationName = isAddMetaData() && addCompilationName(file); boolean addedContributors = isAddMetaData() && addContributors(file); boolean addedIndexCount = isAddMetaData() && addIndexCount(file); if (isAddCoverToFolder()) addCoverToFolder(file); boolean extended = addedMusicBrainzId || addedAttachedPicture || addedLyrics || addedPublisher || addedCompilationName || addedContributors || addedIndexCount; log.info("extended: " + extended + " MusicBrainzId: " + addedMusicBrainzId + " cover: " + addedAttachedPicture + " lyrics: " + addedLyrics + " publisher: " + addedPublisher + " compilation name: " + addedCompilationName + " contributors: " + addedContributors + " index count: " + addedIndexCount); return extended; } private void addCoverToFolder(MP3File file) { byte[] cover = file.getHead().getAttachedPicture(); if (cover == null) return; WindowsMediaPlayerCoverClient wmpClient = new WindowsMediaPlayerCoverClient(); wmpClient.storeCover(file.getFile(), cover); } protected boolean addMusicBrainzId(MP3File file) throws IOException { String musicBrainzId = file.getHead() != null ? file.getHead().getMusicBrainzId() : null; if (musicBrainzId == null) { if (TRM.isSupported()) { TRM trm = new TRM(); try { trm.read(file); if (trm.isValid()) { log.fine("Calculated MusicBrainzId '" + trm.getSignature() + "' for " + file.getFile().getAbsolutePath()); file.getHead().setMusicBrainzId(trm.getSignature()); return true; } else log.severe("Cannot calculate MusicBrainzId for " + file.getFile().getAbsolutePath()); } catch (Throwable t) { log.severe("Cannot calculate MusicBrainzId for " + file.getFile().getAbsolutePath() + ": " + t.getMessage()); } } } else log.info("Found MusicBrainzId '" + musicBrainzId + "' for " + file.getFile().getAbsolutePath()); return false; } protected boolean addCover(MP3File file) { byte[] fileCover = file.getHead() != null ? file.getHead().getAttachedPicture() : null; try { byte[] fetchCover = file.getHead().isCompilation() ? coverClient.fetchCompilationCover(file.getAlbum()) : coverClient.fetchAlbumCover(file.getArtist(), file.getAlbum()); File cachedFile = coverClient.getCachedFile(file.getArtist(), file.getTrack()); byte[] wmpCover = findWindowsMediaPlayerCover(file); byte[] lfCover = findLastFmCover(file); // in case the cached file has been modified but may be shorter if (isFirstNewerThanSecond(cachedFile, file.getFile())) fileCover = null; // find best source byte[] foundCover = fetchCover; if (isFirstBetterThanSecond(wmpCover, foundCover)) foundCover = wmpCover; if (isFirstBetterThanSecond(lfCover, foundCover)) foundCover = lfCover; if (isFirstBetterThanSecond(fileCover, foundCover)) foundCover = fileCover; // store found in coverdb cache if better than peek if (foundCover != null && isFirstBetterThanSecond(foundCover, fetchCover)) { log.fine("Storing found cover (" + foundCover.length + " bytes) in CoverDB cache"); if (file.getHead().isCompilation()) coverClient.storeCompilationCover(file.getAlbum(), foundCover); else coverClient.storeAlbumCover(file.getArtist(), file.getAlbum(), foundCover); } // store in file if better byte[] transformedCover = foundCover != null ? new ImageResizer().resize(foundCover, "jpg", RESIZE_PIXELS, RESIZE_PIXELS) : null; if (transformedCover != null && fileCover != null && isFirstBetterThanSecond(transformedCover, fileCover)) { log.fine("Adding cover (" + transformedCover.length + " bytes) to " + file.getFile().getAbsolutePath()); file.getHead().setCover(transformedCover); return true; } } catch (IOException e) { log.severe("Cannot add cover to " + file.getFile().getAbsolutePath() + ": " + e.getMessage()); } return false; } private boolean isFirstBetterThanSecond(byte[] first, byte[] second) { if (first == null) return false; //noinspection SimplifiableIfStatement if (second == null) return true; return first.length > second.length; } private byte[] findWindowsMediaPlayerCover(MP3File file) throws IOException { WindowsMediaPlayerCoverClient wmpClient = new WindowsMediaPlayerCoverClient(); return wmpClient.findCover(file); } private byte[] findLastFmCover(MP3File file) throws IOException { LastFmCoverClient lfClient = new LastFmCoverClient(); return lfClient.findCover(file); } protected boolean addLyrics(MP3File file) { String fileLyrics = file.getHead() != null ? file.getHead().getLyrics() : null; fileLyrics = lyricsClient.cleanLyrics(fileLyrics); String fetchLyrics = lyricsClient.fetchLyrics(file.getArtist(), file.getTrack()); fetchLyrics = lyricsClient.cleanLyrics(fetchLyrics); File cachedFile = lyricsClient.getCachedFile(file.getArtist(), file.getTrack()); // in case the cached file has been modified but may be shorter if (isFirstNewerThanSecond(cachedFile, file.getFile())) fileLyrics = null; // find best source String foundLyrics = fetchLyrics; if (isFirstBetterThanSecond(fileLyrics, foundLyrics)) foundLyrics = fileLyrics; // store found in lyricsdb cache if better than peek if (foundLyrics != null && isFirstBetterThanSecond(foundLyrics, fetchLyrics)) { log.fine("Storing found lyrics (" + foundLyrics.length() + " bytes) in LyricsDB cache"); lyricsClient.storeLyrics(file.getArtist(), file.getTrack(), foundLyrics); } // store in file if better if (foundLyrics != null && isFirstBetterThanSecond(foundLyrics, fileLyrics)) { log.fine("Adding lyrics (" + foundLyrics.length() + " bytes) to " + file.getFile().getAbsolutePath()); file.getHead().setLyrics(foundLyrics); return true; } return false; } private boolean isFirstNewerThanSecond(File first, File second) { if (first == null) return false; //noinspection SimplifiableIfStatement if (second == null) return true; return (first.lastModified() / 1000) > (second.lastModified() / 1000); } private boolean isFirstBetterThanSecond(String first, String second) { if (first == null) return false; //noinspection SimplifiableIfStatement if (second == null) return true; return first.length() > second.length(); } protected boolean addPublisher(MP3File file) { if (file.getHead().getPublisher() == null) { AmazonMusicClient client = new AmazonMusicClient(); String publisher = client.searchPublisher(file.getArtist(), file.getAlbum()); if (publisher != null) { log.info("Adding publisher '" + publisher + "' for " + file.getFile().getAbsolutePath()); file.getHead().setPublisher(publisher); return true; } } return false; } protected boolean addCompilationName(MP3File file) { if (file.getHead().isCompilation() && file.getHead().getAlbumArtist() == null) { String compilationName = file.getFile().getParentFile().getName(); log.info("Adding compilation name '" + compilationName + "' for " + file.getFile().getAbsolutePath()); file.getHead().setAlbumArtist(compilationName); return true; } return false; } protected boolean addContributors(MP3File file) { Set<String> contributors = new LinkedHashSet<String>(); List<String> artists = parseArtist(file.getArtist()); contributors.addAll(artists.subList(1, artists.size())); List<String> trackArtists = parseTrack(file.getTrack()); contributors.addAll(trackArtists.subList(1, trackArtists.size())); String albumArtist = artists.get(0); String artist = formatContributors(file.getArtist(), new ArrayList<String>(contributors)); if (contributors.size() > 0 && (!file.getArtist().equals(artist) && !(file.getHead().getAlbumArtist() != null && file.getHead().getAlbumArtist().equals(albumArtist)))) { log.info("Adding contributors '" + contributors + "' for " + file.getFile().getAbsolutePath()); file.getHead().setAlbumArtist(albumArtist); file.getHead().setArtist(artist); return true; } return false; } private Maxima determineMaximumIndices(File directory) { Maxima maxima = new Maxima(); // search on same directory level for albums File[] files = directory.listFiles(); if (files != null) { for (File file : files) { MP3File mp3 = MP3File.readValidFile(file); if (mp3 == null) continue; String artistName = mp3.getHead().getAlbumArtist(); if (artistName == null) artistName = mp3.getArtist(); int discIndex = DiscIndexHelper.parseDiscIndex(mp3.getAlbum()); if (discIndex == -1) discIndex = mp3.getPartOfSetIndex(); if (discIndex > 0) maxima.checkIfMaximumDiscIndex(artistName, mp3.getAlbum(), discIndex); int albumIndex = mp3.getIndex(); if (albumIndex > 0) maxima.checkIfMaximumAlbumIndex(artistName, mp3.getAlbum(), discIndex, albumIndex); } } return maxima; } private static class Maxima { private Map<Album, Integer> maximumAlbumIndex = new HashMap<Album, Integer>(); private Map<Album, Integer> maximumDiscIndex = new HashMap<Album, Integer>(); public Integer getMaximumAlbumIndex(String artist, String album, int discIndex) { return maximumAlbumIndex.get(new Album(artist, DiscIndexHelper.formatDiscIndex(album, discIndex))); } public void checkIfMaximumAlbumIndex(String artist, String album, int discIndex, int albumIndex) { Album key = new Album(artist, DiscIndexHelper.formatDiscIndex(DiscIndexHelper.removeDiscIndexPostfix(album), discIndex)); Integer maximum = maximumAlbumIndex.get(key); if (maximum == null || maximum < albumIndex) maximumAlbumIndex.put(key, albumIndex); } public Integer getMaximumDiscIndex(String artist, String album) { return maximumDiscIndex.get(new Album(artist, DiscIndexHelper.removeDiscIndexPostfix(album))); } public void checkIfMaximumDiscIndex(String artist, String album, int discIndex) { Album key = new Album(artist, DiscIndexHelper.removeDiscIndexPostfix(album)); Integer maximum = maximumDiscIndex.get(key); if (maximum == null || maximum < discIndex) maximumDiscIndex.put(key, discIndex); } } private Map<File, Maxima> indexCountCache = new HashMap<File, Maxima>(); protected boolean addIndexCount(MP3File file) { boolean result = false; int discIndex = DiscIndexHelper.parseDiscIndex(file.getAlbum()); if (discIndex > 0) { file.setPartOfSetIndex(discIndex); result = true; } if (file.getIndex() > 0 || file.getPartOfSetIndex() > 0) { // cache for the running of this JVM, locality of the cache should be really good File directory = file.getFile().getParentFile(); Maxima maxima = indexCountCache.get(directory); if (maxima == null) { maxima = determineMaximumIndices(directory); indexCountCache.put(directory, maxima); } Integer maximumAlbumIndex = maxima.getMaximumAlbumIndex(file.getArtist(), file.getAlbum(), file.getPartOfSetIndex()); if (maximumAlbumIndex != null && maximumAlbumIndex != file.getCount()) { file.setCount(maximumAlbumIndex); result = true; } Integer maximumDiscIndex = maxima.getMaximumDiscIndex(file.getArtist(), file.getAlbum()); if (maximumDiscIndex != null && maximumDiscIndex != file.getPartOfSetCount()) { file.setPartOfSetCount(maximumDiscIndex); result = true; } } if (discIndex > 0) { file.setAlbum(DiscIndexHelper.removeDiscIndexPostfix(file.getAlbum())); } return result; } public static void main(String[] args) throws Exception { if (args.length != 1) { System.out.println("slash.metamusic.mp3.tools.MP3Extender <file>"); System.exit(1); } File file = new File(args[0]); MP3Extender extender = new MP3Extender(); extender.extend(file); System.exit(0); } }