package net.kirke.mp3dj;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import net.kirke.mp3dj.resources.Msgs;
import net.kirke.mp3dj.resources.Props;
/*
Using MpegAudioSPI1.9.4 to parse mp3 tags (not for playing) from:
http://www.javazoom.net/mp3spi/sources.html
http://www.javazoom.net/mp3spi/docs/doc1.9.4/javazoom/spi/mpeg/sampled/file/MpegAudioFileFormat.html
http://www.jsresources.org/faq_tritonus.html
MP3SPI is a SPI (Service Provider Interface) that adds MP3 support for
JavaSound. It allows you to play MPEG 1/2/2.5 Layer 1/2/3 files thanks
to underlying JavaLayer and Tritonus libraries. This is a non-commercial
project and anyone can add his contribution. MP3SPI is licensed under LGPL
(see LICENSE.txt).
# AudioFormat properties :
- bitrate : [Integer], bitrate in bits per seconds, average bitrate for
VBR enabled stream.
- vbr : [Boolean], VBR flag
# AudioFileFormat properties :
Standard parameters :
- duration : [Long], duration in microseconds.
- title : [String], Title of the stream.
- author : [String], Name of the artist of the stream.
- album : [String], Name of the album of the stream.
- date : [String], The date (year) of the recording or release of the stream.
- copyright : [String], Copyright message of the stream.
- comment : [String], Comment of the stream.
Extended MP3 parameters :
- mp3.version.mpeg : [String], mpeg version : 1,2 or 2.5
- mp3.version.layer : [String], layer version 1, 2 or 3
- mp3.version.encoding : [String], mpeg encoding : MPEG1, MPEG2-LSF,
MPEG2.5-LSF
- mp3.channels : [Integer], number of channels 1 : mono, 2 : stereo.
- mp3.frequency.hz : [Integer], sampling rate in hz.
- mp3.bitrate.nominal.bps : [Integer], nominal bitrate in bps.
- mp3.length.bytes : [Integer], length in bytes.
- mp3.length.frames : [Integer], length in frames.
- mp3.framesize.bytes : [Integer], framesize of the first frame.
framesize is not constant for VBR streams.
- mp3.framerate.fps : [Float], framerate in frames per seconds.
- mp3.header.pos : [Integer], position of first audio header (or ID3v2 size).
- mp3.vbr : [Boolean], vbr flag.
- mp3.vbr.scale : [Integer], vbr scale.
- mp3.crc : [Boolean], crc flag.
- mp3.original : [Boolean], original flag.
- mp3.copyright : [Boolean], copyright flag.
- mp3.padding : [Boolean], padding flag.
- mp3.mode : [Integer], mode 0:STEREO 1:JOINT_STEREO 2:DUAL_CHANNEL
3:SINGLE_CHANNEL
- mp3.id3tag.genre : [String], ID3 tag (v1 or v2) genre.
- mp3.id3tag.track : [String], ID3 tag (v1 or v2) track info.
- mp3.id3tagv2 : [InputStream], ID3v2 frames.
- mp3.shoutcast.metadata.key : [String], Shoutcast meta key with matching
System.out.println(audio);
bitrate=128000
vbr=false
System.out.println(mp3);
album=Blue Horse
author=Be Good Tanyas
date=2001
duration=299389000
mp3.bitrate.nominal.bps=128000
mp3.channels=2
mp3.copyright=false
mp3.crc=false
mp3.framerate.fps=38.28125
mp3.framesize.bytes=414
mp3.frequency.hz=44100
mp3.header.pos=10240
mp3.id3tag.genre=(80)
mp3.id3tag.track=12/12
mp3.id3tag.v2=java.io.ByteArrayInputStream@e6f7d2
mp3.length.bytes=4779572
mp3.length.frames=11461
mp3.mode=1
mp3.original=false
mp3.padding=true
mp3.vbr=false
mp3.vbr.scale=0
mp3.version.encoding=MPEG1L3
mp3.version.layer=3
mp3.version.mpeg=1
*/
/**
* Refresh 'files' database table.
*
* @author Kirk Erickson (latest modification by $Author: kirke $)
* @version $Revision: 1.5 $ $Date: 2008-06-21 04:46:35 $
*/
public class RefreshFiles implements Serializable {
Connection conn;
Statement stmt;
PreparedStatement deleteFile;
PreparedStatement insertFile;
ResultSet rs;
int totalFiles;
RefreshFolders folders;
RefreshGenres genres;
Msgs msgs;
private static Props props;
private String mp3Dir;
public RefreshFiles(Connection connection,
String mp3Directory,
RefreshFolders refreshFolders,
RefreshGenres refreshGenres) {
conn = connection;
mp3Dir=mp3Directory;
deleteFile = null;
insertFile = null;
folders = refreshFolders;
genres = refreshGenres;
msgs = new Msgs();
rs = null;
totalFiles = 0;
try {
stmt = conn.createStatement();
DatabaseMetaData metadata = conn.getMetaData();
rs = metadata.getTables(null, null, "FILES", null);
if (!rs.next()) {
stmt.executeUpdate( "CREATE TABLE files (" +
"id IDENTITY NOT NULL," +
"folder VARCHAR(255) NOT NULL," +
"name VARCHAR(255) NOT NULL," +
"album VARCHAR(255) NOT NULL," +
"duration VARCHAR(10) NOT NULL," +
"bitrate INTEGER NOT NULL," +
"kilobytes INTEGER NOT NULL," +
"track VARCHAR(10) NOT NULL," +
"year VARCHAR(6) NOT NULL," +
"modified VARCHAR(16) NOT NULL," +
"artist VARCHAR(255) NOT NULL," +
"title VARCHAR(255) NOT NULL," +
"comment VARCHAR(255) NOT NULL," +
"milliseconds INTEGER NOT NULL," +
"messages VARCHAR(255) NOT NULL," +
"genre VARCHAR(255) NOT NULL," +
"PRIMARY KEY( id )" +
")" );
}
deleteFile = conn.prepareStatement(
"DELETE FROM files WHERE id = ?");
insertFile = conn.prepareStatement(
"INSERT INTO files VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
} catch (SQLException x) {
System.out.println("RefreshFiles: " + x.getMessage());
System.out.println("RefreshFiles: " + x.getErrorCode());
}
}
/**
* Delete file identified by 'id' from the files table.
*/
public void delete(int id) {
try {
if (conn.isClosed()) return;
deleteFile.setInt(1, id);
deleteFile.executeUpdate();
} catch (SQLException x) {
System.out.println("delete: " + x.getMessage());
System.out.println("delete: " + x.getErrorCode());
x.printStackTrace();
}
}
/**
* Add file 'dir' to the 'files' table.
*/
public void insert(File dir) {
String folder = "";
String name = "";
String messages = "";
try {
// id
insertFile.setString(1, null);
// folder
folder = dir.getParent();
if (folder.length() > mp3Dir.length()) {
// strip mp3.dir and following separator, if any
int i = mp3Dir.length();
if (folder.charAt(i) == File.separatorChar) {
folder = folder.substring(i+1);
} else {
folder = folder.substring(i);
}
} else {
folder = ".";
}
if (File.separatorChar == '\\') {
// change Windows separator (backslash) to forward slash
folder = folder.replace(File.separatorChar, '/');
}
insertFile.setString(2, folder);
// name
name = dir.getName();
if (!name.toLowerCase().endsWith(".mp3")) {
// skip (no .mp3 suffix)
totalFiles--;
return;
}
// ignore if already inserted with same modified timestamp
PreparedStatement pstmt = conn.prepareStatement(
"SELECT * FROM files WHERE (folder = ?) AND (name = ?)");
pstmt.setString(1, folder);
pstmt.setString(2, name);
rs = pstmt.executeQuery();
while (rs.next()) {
SimpleDateFormat f = new SimpleDateFormat("yyyy-MMdd-HHmm");
Date d = new Date(dir.lastModified());
if (rs.getString(10).equals(f.format(d))) {
return;
} else {
folders.delete(rs.getString(2));
genres.delete(rs.getString(16));
delete(rs.getInt(1));
}
}
insertFile.setString(3, name);
AudioFileFormat baseFileFormat = null;
AudioFormat baseFormat = null;
baseFileFormat = AudioSystem.getAudioFileFormat(dir);
if (baseFileFormat == null) {
System.out.println("insert: SKIPPED: " + name + "(baseFileFormat == null)");
return;
}
baseFormat = baseFileFormat.getFormat();
if (baseFormat == null) {
if (messages.length() > 0) {
messages += ", ";
}
messages += msgs.getMessage("NO_AUDIO_PROPERTIES", null);
} else {
Map mp3 = baseFileFormat.properties();
Map audio = baseFormat.properties();
if (mp3 == null) {
System.out.println("insert: SKIPPED: " + name + "(no baseFormat.properties()");
return;
}
// album
String album = (String) mp3.get("album");
if (album == null) {
album = "";
}
insertFile.setString(4, album);
// duration (microseconds)
// A microsecond is one millionth of a second.
// A millisecond (ms) is one thousandth of a second.
// Prefer TLEN (mp3.id3tag.length), fallback on duration.
Long milliseconds = null;
if (mp3.get("mp3.id3tag.length") == null) {
if (mp3.get("duration") == null) {
if (messages.length() > 0) {
messages += ", ";
}
messages += msgs.getMessage("NO_DURATION", null);
return;
} else {
milliseconds = (Long) mp3.get("duration")/1000;
}
} else {
milliseconds = Long.valueOf((String)mp3.get("mp3.id3tag.length"));
}
int mins = (int) Math.floor(milliseconds/1000 / 60);
int secs = (int) Math.floor(milliseconds/1000) % 60;
String duration = mins+":"+(secs < 10?"0"
+ Integer.toString(secs):Integer.toString(secs));
insertFile.setString(5, duration);
// bitrate
int bitrate = (Integer) audio.get("bitrate");
bitrate = bitrate/1000;
// int bitrate2 = (Integer) mp3.get("mp3.bitrate.nominal.bps")/1000;
// System.out.println("insert: VBR " + name + " " + bitrate + "/" + bitrate2);
insertFile.setInt(6, bitrate);
if ((Boolean)audio.get("vbr")) {
// if (messages.length() > 0) {
// messages += ", ";
// }
// messages += msgs.getMessage("VARIABLE_BITRATE", null);
}
else if (
bitrate != 16 && bitrate != 24
&& bitrate != 32 && bitrate != 40
&& bitrate != 48 && bitrate != 56
&& bitrate != 64 && bitrate != 80
&& bitrate != 96 && bitrate != 112
&& bitrate != 128 && bitrate != 160
&& bitrate != 192 && bitrate != 224
&& bitrate != 256 && bitrate != 320) {
Object params[] = { bitrate };
if (messages.length() > 0) {
messages += ", ";
}
messages += msgs.getMessage("UNRECOGNIZED_BITRATE", params);
}
// kilobytes
insertFile.setLong(7, dir.length()/1024);
// track
String track = (String) mp3.get("mp3.id3tag.track");
if (track == null) {
track = "";
}
insertFile.setString(8, track);
// year
String year = (String) mp3.get("date");
if (year == null) {
year = "";
}
insertFile.setString(9, year);
// modified
SimpleDateFormat f = new SimpleDateFormat("yyyy-MMdd-HHmm");
Date d = new Date(dir.lastModified());
insertFile.setString(10, f.format(d));
// artist
String artist = (String) mp3.get("author");
if (artist == null) {
if (messages.length() > 0) {
messages += ", ";
}
messages += msgs.getMessage("NO_ARTIST", null);
artist = "";
}
artist = artist.trim();
insertFile.setString(11, artist);
// title
String title = (String) mp3.get("title");
if (title == null) {
if (messages.length() > 0) {
messages += ", ";
}
messages += msgs.getMessage("NO_TITLE", null);
title = "";
}
title = title.trim();
insertFile.setString(12, title);
// bad filename (not "artist - title.mp3")
if (artist.length() > 0) {
String correct = artist + " - " + title + ".mp3";
correct = correct.replace('*','+');
correct = correct.replace('?',' ');
correct = correct.replace(':','-');
correct = correct.replace('"','\'');
correct = correct.replace('/','-');
if (!name.equals(correct)) {
if (messages.length() > 0) {
messages += ", ";
}
messages += msgs.getMessage("BAD_FILENAME", null);
System.out.println("insert: correct=" + correct);
System.out.println("insert: name=" + name);
}
}
// comment
String comment = (String) mp3.get("comment");
if (comment == null) {
comment = "";
}
insertFile.setString(13, comment.trim());
// milliseconds
insertFile.setInt(14, milliseconds.intValue());
// messages
insertFile.setString(15, messages);
if (messages != "") {
System.out.println("insert: MESSAGE: " + folder + "/"
+ name + " (" + messages + ")");
}
// genre maybe number mapped by genres.insert()
// ex. (17) yields Rock
String genre = (String) mp3.get("mp3.id3tag.genre");
if (genre == null) {
genre = "Unknown";
}
genre = genres.insert(genre); // we get genre text back
insertFile.setString(16, genre); // for grid readability
}
if (conn.isClosed()) return;
insertFile.executeUpdate();
if (folder.equals(".")) {
System.out.println("insert: " + mp3Dir + File.separator + name);
} else {
System.out.println("insert: " + mp3Dir + File.separator + folder
+ File.separator + name);
}
folders.insert(folder);
} catch(UnsupportedAudioFileException x) {
Object params[] = { folder, name };
String msg = msgs.getMessage("SKIPPED_UNSUPPORTED_AUDIO_FILE",
params);
System.out.println("insert: " + msg);
} catch(EOFException x) {
Object params[] = { folder, name };
String msg = msgs.getMessage("SKIPPED_UNEXPECTED_EOF", params);
System.out.println("insert: " + msg);
} catch(IOException x) {
System.out.println("insert: " + x.getMessage());
} catch(SQLException x) {
System.out.println("insert: " + x.getMessage());
} catch(Exception x) {
System.out.println("insert: UNRECOGNIZED EXCEPTION: " + x);
System.out.println("insert: SKIPPING " + name
+ "(" + x.getMessage() + ")");
x.printStackTrace();
}
}
}