/*
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-2003 Christian Pesch. All Rights Reserved.
*/
package slash.metamusic.mp3;
import slash.metamusic.util.Files;
import slash.metamusic.util.StringHelper;
import java.io.File;
import java.io.IOException;
import java.util.StringTokenizer;
import java.util.logging.Logger;
/**
* My instances represent an ID3 compatible file name.
*
* @author Christian Pesch
* @version $Id: ID3FileName.java 960 2007-03-24 21:07:08Z cpesch $
*/
public class ID3FileName implements ID3MetaData {
public static final int WINDOWS_PATH_LENGTH_LIMIT = 255;
/**
* Logging output
*/
protected static final Logger log = Logger.getLogger(ID3FileName.class.getName());
private String delimiter = "-.";
public ID3FileName() {
}
public ID3FileName(File file) throws IOException {
if (!read(file))
throw new IOException("Cannot read file name of " + file);
}
public ID3FileName(String track, String artist, String album, int index, boolean various) {
initialize(track, artist, album, index, various, true);
}
private void initialize(String track, String artist, String album, int index, boolean various, boolean valid) {
setTrack(track);
setArtist(artist);
setAlbum(album);
setIndex(index);
setVarious(various);
this.valid = valid;
}
private void initialize() {
initialize(null, null, null, -1, false, false);
}
// --- read/write object -----------------------------------
private boolean isInvalidChar(char c) {
return c == '.' || c == '-' || c == '/' || c == '\\';
}
private String trimWhileReading(String str) {
str = str.trim();
if (str.length() > 0) {
if (isInvalidChar(str.charAt(0))) {
if (str.length() > 1) {
str = str.substring(1, str.length() - 1);
} else {
str = "";
}
}
}
return str;
}
private String trimForWriting(String str) {
if (str == null)
return null;
str = str.trim();
str = str.replace("\\", "");
str = str.replace("/", "");
str = str.replace(File.separator, "");
str = str.replace("\"", "");
str = str.replace("?", "");
str = str.replace("*", "");
str = str.replace(":", "");
str = str.replace(" - ", "-");
str = str.replace("`", "'");
str = str.replace("!", "");
return str;
}
public void setDelimiter(String delimiter) { // TODO should take regex or some other configurable parse string
this.delimiter = delimiter;
}
/**
* Read MP3 file name from file
*
* @param file the File to read from
* @throws IOException if an error occurs
*/
public boolean read(File file) throws IOException {
initialize();
this.file = file;
log.fine("Analysing file name of " + file.getAbsolutePath());
// TODO make parsing configurable like juk does
StringTokenizer tokenizer = new StringTokenizer(file.getName(), delimiter, true);
if (tokenizer.hasMoreTokens()) {
artist = trimWhileReading(tokenizer.nextToken());
}
String temp = null;
while (tokenizer.hasMoreTokens() && (temp == null || temp.length() == 0)) {
temp = trimWhileReading(tokenizer.nextToken());
}
album = temp;
valid = true;
temp = null;
while (tokenizer.hasMoreTokens() && (temp == null || temp.length() == 0)) {
temp = trimWhileReading(tokenizer.nextToken());
try {
index = Integer.parseInt(temp);
// track number found
temp = null;
break;
} catch (NumberFormatException e) {
// ignore
}
}
while (tokenizer.hasMoreTokens() && (temp == null || temp.length() == 0)) {
temp = trimWhileReading(tokenizer.nextToken());
}
track = temp;
// no album found: track is album
if (track == null || track.toLowerCase().equals("mp3")) {
track = album;
album = null;
}
// not even track found
if (track != null && track.toLowerCase().equals("mp3")) {
track = null;
}
return valid;
}
/**
* Write MP3 file name to this file, i.e. rename
* and move it.
*
* @throws IOException if an error occurs
*/
public void write() throws IOException {
write(getFile());
}
/**
* Write MP3 file name to the file, i.e. rename
* and move it.
*
* @param file the File to write to
* @return the file after the write operation
* @throws IOException if an error occurs
*/
public File write(File file) throws IOException {
file = rename(file);
file = move(file);
return file;
}
private int getOversize(File file, String fileName) {
return file.getParent().length() + 1 + fileName.length() - WINDOWS_PATH_LENGTH_LIMIT;
}
/**
* Rename the file, i.e. rename the file to a MP3 file name
* compliant name
*
* @param file the File to rename
* @return the file after the rename operation
* @throws IOException if an error occurs
*/
public File rename(File file) throws IOException {
String fileName = getFileName(file);
// work around Windows path length limitations
int oversize = getOversize(file, fileName);
if (oversize > 0) {
log.warning("Path " + fileName + " is " + oversize + " chars too long");
fileName = createFileName(file, getArtist(), getAlbum(),
StringHelper.shortenString(getTrack(), 3, oversize), getIndex(), getPartOfSetIndex());
}
oversize = getOversize(file, fileName);
if (oversize > 0) {
log.warning("Path " + fileName + " is " + oversize + " chars too long");
fileName = createFileName(file, getArtist(),
StringHelper.shortenString(getAlbum(), 3, oversize),
StringHelper.shortenString(getTrack(), 3, getTrack().length()), getIndex(), getPartOfSetIndex());
}
oversize = getOversize(file, fileName);
if (oversize > 0) {
log.warning("Path " + fileName + " is " + oversize + " chars too long");
fileName = createFileName(file, StringHelper.shortenString(getArtist(), 3, oversize),
StringHelper.shortenString(getAlbum(), 3, getAlbum().length()),
StringHelper.shortenString(getTrack(), 3, getTrack().length()), getIndex(), getPartOfSetIndex());
}
oversize = getOversize(file, fileName);
if (oversize > 0) {
log.severe("Path " + fileName + " is " + oversize + " chars too long");
throw new IOException("Cannot shorten file name of " + file + "; " + fileName + " still " + oversize + " chars too long");
}
if (!file.getName().equals(fileName)) {
File newFile = new File(file.getParentFile(), fileName);
// work around Windows case insensitivity for file names by renaming source first
if (newFile.getAbsolutePath().toLowerCase().equals(file.getAbsolutePath().toLowerCase())) {
File renameFile = new File(file.getParentFile(), "cAsE" + file.getName());
if (file.renameTo(renameFile))
file = renameFile;
}
boolean exists = newFile.exists();
if (!exists && file.renameTo(newFile)) {
log.fine("Renamed file " + file + " to " + newFile);
file = newFile;
} else {
log.severe("Cannot rename file " + file + " to " + newFile);
throw new IOException("Cannot rename file " + file + " to " + newFile + ", target exists");
}
}
return file;
}
/**
* Move the file, i.e. move the file to a MP3 file name
* compliant path
*
* @param file the File to move
* @return the file after the move operation
* @throws IOException if an error occurs
*/
public File move(File file) throws IOException {
String currentPath = file.getParentFile().getAbsolutePath();
String filePath = getFilePath();
if (!currentPath.endsWith(filePath)) {
File newPath = new File(file.getParentFile(), filePath);
if (!newPath.exists())
if (!newPath.mkdirs())
log.severe("Cannot create path to " + newPath);
File newFile = new File(newPath, file.getName());
boolean exists = newFile.exists();
if (!exists && file.renameTo(newFile)) {
log.fine("Moved file " + file + " to " + newPath);
file = newFile;
} else {
log.severe("Cannot move file " + file + " to " + newFile);
throw new IOException("Cannot move file " + file + " to " + newFile + ", target exists");
}
}
return file;
}
// --- get object ------------------------------------------
public File getFile() {
return file;
}
public String getFileName() {
return createFileName(getFile(), getArtist(), getAlbum(), getTrack(), getIndex(), getPartOfSetIndex());
}
private String getFileName(File file) {
return createFileName(file, getArtist(), getAlbum(), getTrack(), getIndex(), getPartOfSetIndex());
}
private String createFileName(File file, String artist, String album, String track, int index, int partOfSetIndex) {
artist = trimForWriting(artist);
if (artist == null)
return trimForWriting(file.getName());
track = trimForWriting(track);
if (track == null)
return trimForWriting(file.getName());
album = trimForWriting(album);
String indexStr = index != -1 ? StringHelper.formatNumber(index, 2) : null;
String partOfSetIndexStr = partOfSetIndex != -1 ? StringHelper.formatNumber(partOfSetIndex, 1) : null;
// TODO make this pattern configurable
return artist + " - " +
(album != null ? album : "") +
(partOfSetIndexStr != null ? " (disc " + partOfSetIndexStr + ")" : "") +
(album != null ? " - " : "") +
(indexStr != null ? indexStr + " - " : "") +
track + ".mp3";
}
public String getFilePath() {
String artist = getArtist();
if (artist == null)
return file.getAbsolutePath();
String album = getAlbum();
if (album == null)
return file.getAbsolutePath();
if (isVarious())
return Files.replaceSeparators(trimForWriting(album));
else
return Files.replaceSeparators(trimForWriting(artist) + "/" + trimForWriting(album));
}
public String getAbsolutePath() {
return Files.replaceSeparators(getFilePath() + "/" + getFileName());
}
// --- MetaData get ----------------------------------------
public boolean isValid() {
return valid;
}
public long getReadSize() {
return -1;
}
public String getTrack() {
return track;
}
public String getArtist() {
return artist;
}
public String getAlbum() {
return album;
}
public int getYear() {
return -1;
}
public ID3Genre getGenre() {
return null;
}
public String getComment() {
return null;
}
public int getIndex() {
return index;
}
public int getPartOfSetIndex() {
return partOfSetIndex;
}
public boolean isVarious() {
return various;
}
// --- MetaData set ----------------------------------------
public void setTrack(String newTrack) {
this.track = newTrack;
}
public void setArtist(String newArtist) {
this.artist = newArtist;
}
public void setAlbum(String newAlbum) {
this.album = newAlbum;
}
public void setYear(int newYear) {
}
public void setGenre(ID3Genre newGenre) {
}
public void setIndex(int newIndex) {
this.index = newIndex;
}
public void setPartOfSetIndex(int newIndex) {
this.partOfSetIndex = newIndex;
}
public void setComment(String newComment) {
}
public void setVarious(boolean various) {
this.various = various;
}
public static void main(String[] args) throws IOException {
if (args.length == 0) {
System.out.println("slash.metamusic.mp3.ID3FileName <file1> <file2> ... <fileN>");
System.exit(1);
}
for (String arg : args) {
ID3FileName fileName = new ID3FileName();
fileName.read(new File(arg));
log.info(fileName.toString());
}
System.exit(0);
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("ID3FileName[" + "file=").append(file != null ? file.getAbsolutePath() : "<null>").append(",").
append("valid=").append(isValid());
buffer.append(", " + "track=").append(getTrack()).
append(", " + "artist=").append(getArtist()).
append(", " + "album=").append(getAlbum()).
append(", " + "index=").append(getIndex()).
append(", " + "partOfSet=").append(getPartOfSetIndex());
buffer.append("]");
return buffer.toString();
}
// --- member variables ------------------------------------
protected File file;
protected boolean valid;
protected String track, artist, album;
protected int index = -1, partOfSetIndex = -1;
protected boolean various;
}