/* You may freely copy, distribute, modify and use this class as long as the original author attribution remains intact. See message below. Copyright (C) 2003 Christian Pesch. All Rights Reserved. */ package slash.metamusic.coverdb; import slash.metamusic.hex.HexEncoder; import slash.metamusic.mp3.ID3v2Frame; import slash.metamusic.mp3.ID3v2Header; import slash.metamusic.mp3.MP3File; import slash.metamusic.util.URLLoader; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.logging.Logger; /** * A client that looks in the filesystem for Windows Media Player cover files, * which are put separately next to the media files. * <p/> * That is * <ul> * <li>AlbumArt_{ALBUM_ID}_Large.jpg</li> * <li>AlbumArt_{ALBUM_ID}_Small.jpg</li> * <li>Folder.jpg</li> * <li>AlbumArtSmall.jpg</li> * <li>desktop.ini</li> * </ul> * * @author Christian Pesch * @version $Id: WindowsMediaPlayerCoverClient.java 797 2006-04-23 20:28:05Z cpesch $ */ public class WindowsMediaPlayerCoverClient extends FileSystemCoverClient { private static final String FOLDER_JPG_FILE_NAME = "Folder" + ALBUM_ART_FILE_NAME_EXTENSION; private static final String FOLDER_SMALL_JPG_FILE_NAME = "AlbumArtSmall" + ALBUM_ART_FILE_NAME_EXTENSION; private static final String DESKTOP_INI_FILE_NAME = "desktop.ini"; private static final String ALBUM_ART_FILE_NAME_PREFIX = "AlbumArt"; private static final String WM_COLLECTION_ID_PREFIX = "WM/WMCollectionID"; static { log = Logger.getLogger(WindowsMediaPlayerCoverClient.class.getName()); } private String asAlbumArtId(byte[] bytes) { String hexId = HexEncoder.encodeBytes(bytes); // 3rd and 4th char wrong: Backstreet Boys 3F instead of 81 // 3rd and 4th char after first dash wrong: The Psychedelic Furs 3F 8D // The Smashing Pumpkins 3F 8F // 3rd and 4th char after second dash wrong: The Judds 3F 90 if (hexId.length() < 22) return null; return hexId.substring(8, 10) + hexId.substring(6, 8) + hexId.substring(4, 6) + hexId.substring(2, 4) + "-" + hexId.substring(12, 14) + hexId.substring(10, 12) + "-" + hexId.substring(16, 18) + hexId.substring(14, 16) + "-" + hexId.substring(18, 22) + "-" + hexId.substring(22, hexId.length()); } public String findWindowsMediaAlbumId(MP3File file) { ID3v2Header head = file.getHead(); for (ID3v2Frame f : head.getFrames()) { String content = f.getStringContent(); if (f.getTagName().equals("PRIV")) { if (content.startsWith(WM_COLLECTION_ID_PREFIX)) { content = content.substring(WM_COLLECTION_ID_PREFIX.length()); content = content.substring(0, content.indexOf('<')); return asAlbumArtId(content.getBytes()); } } } return null; } private File findMatchingAlbumArtFile(File directory, final String albumId) { File[] files = directory.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { if (!name.startsWith(ALBUM_ART_FILE_NAME_PREFIX + "_{")) return false; if (!name.endsWith("}_Large" + ALBUM_ART_FILE_NAME_EXTENSION)) return false; int beginIndex = name.indexOf('{'); int endIndex = name.indexOf('}'); if (beginIndex == -1 || endIndex == -1) return false; String idFromName = name.substring(beginIndex + 1, endIndex); return idFromName.substring(0, 2).equals(albumId.substring(0, 2)) && idFromName.substring(4, 11).equals(albumId.substring(4, 11)) && idFromName.substring(13, 16).equals(albumId.substring(13, 16)) && idFromName.substring(18).endsWith(albumId.substring(18)); } }); return files.length > 0 ? files[0] : null; } private int countAlbumArtFiles(File directory) { File[] files = directory.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.startsWith(ALBUM_ART_FILE_NAME_PREFIX + "_{") && name.endsWith(ALBUM_ART_FILE_NAME_EXTENSION); } }); return files.length; } private byte[] load(File file) throws IOException { byte[] result = URLLoader.getContents(file); if (result.length > 1) { // sometimes !%#&! WMP does not add FFD8 prefix if (result[0] == (byte) 0xFF && result[1] == (byte) 0xE0) { byte[] copy = new byte[result.length + 2]; System.arraycopy(result, 0, copy, 2, result.length); copy[0] = (byte) 0xFF; copy[1] = (byte) 0xD8; result = copy; } } return result; } public byte[] findCover(MP3File file) throws IOException { File parent = file.getFile().getParentFile(); // first fetch from Windows Media tags String albumId = findWindowsMediaAlbumId(file); if (albumId != null) { File albumArtLargeJpg = new File(parent, ALBUM_ART_FILE_NAME_PREFIX + "_{" + albumId + "}_Large" + ALBUM_ART_FILE_NAME_EXTENSION); if (albumArtLargeJpg.exists()) return load(albumArtLargeJpg); File albumArtSmallJpg = new File(parent, ALBUM_ART_FILE_NAME_PREFIX + "_{" + albumId + "}_Small" + ALBUM_ART_FILE_NAME_EXTENSION); if (albumArtSmallJpg.exists()) return load(albumArtSmallJpg); // some bytes seem to be not understood, be a little more lenient File albumArtMatchingJpg = findMatchingAlbumArtFile(parent, albumId); if (albumArtMatchingJpg != null) return load(albumArtMatchingJpg); } // if not yet successful fetch from folder but only if there is no AlbumArt_{ID} file if (countAlbumArtFiles(parent) > 0) return null; File folderJpg = new File(parent, FOLDER_JPG_FILE_NAME); if (folderJpg.exists()) return load(folderJpg); File folderSmallJpg = new File(parent, FOLDER_SMALL_JPG_FILE_NAME); if (folderSmallJpg.exists()) return load(folderSmallJpg); return null; } public void storeCover(File file, byte[] cover) { storeCover(file.getParentFile(), FOLDER_JPG_FILE_NAME, cover); } public void removeCover(File file) { File parent = file; if (file.isFile()) parent = file.getParentFile(); removeFile(new File(parent, FOLDER_JPG_FILE_NAME)); removeFile(new File(parent, DESKTOP_INI_FILE_NAME)); File[] files = parent.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.startsWith(ALBUM_ART_FILE_NAME_PREFIX) && name.endsWith(ALBUM_ART_FILE_NAME_EXTENSION); } }); if (files != null) { for (File f : files) { removeFile(f); } } } public static void main(String[] args) throws Exception { if (args.length != 1) { System.out.println("slash.metamusic.coverdb.WindowsMediaPlayerCoverClient <file>"); System.exit(1); } MP3File file = MP3File.readValidFile(new File(args[0])); WindowsMediaPlayerCoverClient client = new WindowsMediaPlayerCoverClient(); client.findCover(file); System.exit(0); } }