Package com.tulskiy.musique.playlist

Source Code of com.tulskiy.musique.playlist.Playlist

/*
* Copyright (c) 2008, 2009, 2010, 2011 Denis Tulskiy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with this work.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.tulskiy.musique.playlist;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Map.Entry;
import java.util.logging.Logger;

import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.datatype.Pair;

import com.tulskiy.musique.audio.AudioFileReader;
import com.tulskiy.musique.gui.model.FieldValues;
import com.tulskiy.musique.gui.playlist.PlaylistColumn;
import com.tulskiy.musique.gui.playlist.SeparatorTrack;
import com.tulskiy.musique.playlist.formatting.Parser;
import com.tulskiy.musique.playlist.formatting.tokens.Expression;
import com.tulskiy.musique.system.TrackIO;
import com.tulskiy.musique.util.AudioMath;
import com.tulskiy.musique.util.Util;

/**
* Author: Denis Tulskiy
* Date: Dec 30, 2009
*/
public class Playlist extends ArrayList<Track> {

  private static final String OLD_META_KEY_COMMENT = "comment";
  private static final String OLD_META_KEY_GENRE = "genre";
  private static final String OLD_META_KEY_YEAR = "year";
  private static final String OLD_META_KEY_TOTAL_DISCS = "totalDiscs";
  private static final String OLD_META_KEY_DISC_NUMBER = "discNumber";
  private static final String OLD_META_KEY_TOTAL_TRACKS = "totalTracks";
  private static final String OLD_META_KEY_TRACK_NUMBER = "trackNumber";
  private static final String OLD_META_KEY_TITLE = "title";
  private static final String OLD_META_KEY_ALBUM_ARTIST = "albumArtist";
  private static final String OLD_META_KEY_ALBUM = "album";
  private static final String OLD_META_KEY_ARTIST = "artist";
  private static final String META_KEY_CODEC = "codec";
    private static final String META_KEY_ENCODER = "encoder";

  private static MessageFormat format = new MessageFormat("\"{0}\" \"{1}\" {2}");

    private static final int VERSION = 3;
    private static final byte[] MAGIC = "BARABASHKA".getBytes();

    private final Logger logger = Logger.getLogger(getClass().getName());
    private ArrayList<PlaylistListener> listeners = new ArrayList<PlaylistListener>();
    private String name;
    private boolean sortAscending = true;
    private String sortBy;
    private String groupBy;
    private Expression groupExpression;
    private boolean libraryView;

    private List<PlaylistColumn> columns;

    @Deprecated
    public Playlist(String fmt) {
        try {
            Object[] objects = format.parse(fmt);
            setName((String) objects[0]);
            setGroupBy((String) objects[1]);
            setLibraryView(Boolean.valueOf((String) objects[2]));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Playlist() {

    }

    public void cleanUp() {
        Iterator<Track> it = iterator();
        while (it.hasNext()) {
            Track next = it.next();
            if (next instanceof SeparatorTrack)
                it.remove();
        }
    }

    public void save(File file) {
        try {
            //remove the garbage
            cleanUp();
            logger.fine("Saving playlist: " + file.getName());
            DataOutputStream dos = new DataOutputStream(
                    new BufferedOutputStream(new FileOutputStream(file)));
            dos.write(MAGIC);
            dos.writeInt(VERSION);
            dos.writeInt(size());
            List<Pair> meta = new LinkedList<Pair>();
            TrackData trackData;
            for (Track track : this) {
              trackData = track.getTrackData();
              trackData.removeEmptyTagFields();
                dos.writeUTF(trackData.getLocation().toString());
                dos.writeLong(trackData.getStartPosition());
                dos.writeLong(trackData.getTotalSamples());
                dos.writeInt(trackData.getSubsongIndex());
                if (trackData.getSubsongIndex() > 0) {
                    dos.writeBoolean(trackData.isCueEmbedded());
                    if (!trackData.isCueEmbedded())
                        dos.writeUTF(trackData.getCueLocation());
                }
                dos.writeInt(trackData.getBps());
                dos.writeInt(trackData.getChannels());
                dos.writeInt(trackData.getSampleRate());
                dos.writeInt(trackData.getBitrate());
                dos.writeLong(trackData.getDateAdded());
                dos.writeLong(trackData.getLastModified());

                meta.clear();
                if (!Util.isEmpty(trackData.getCodec()))
                    meta.add(new Pair(META_KEY_CODEC, trackData.getCodec()));

                if (!Util.isEmpty(trackData.getEncoder())) {
                  meta.add(new Pair(META_KEY_ENCODER, trackData.getEncoder()));
                }
                Iterator<Entry<FieldKey, FieldValues>> fields = trackData.getAllTagFieldValuesIterator();
                if (fields != null) {
                  while (fields.hasNext()) {
                    Entry<FieldKey, FieldValues> field = fields.next();
                for (int i = 0; i < field.getValue().size(); i++) {
                  String value = field.getValue().get(i);
                        meta.add(new Pair(field.getKey().toString(), value));
                      }
                  }
                }

                dos.writeInt(meta.size());
                for (Pair pair : meta) {
                    dos.writeUTF(pair.getKey());
                    dos.writeUTF(pair.getValue());
                }
            }

            dos.close();
            regroup();
        } catch (IOException e) {
            logger.warning("Failed to save playlist " + file.getName() + ": " + e.getMessage());
        }
    }

    public void load(File file) {
        try {
            TrackDataCache cache = TrackDataCache.getInstance();
            logger.fine("Loading musique playlist: " + file.getName());
            DataInputStream dis = new DataInputStream(
                    new BufferedInputStream(new FileInputStream(file)));

            byte[] b = new byte[MAGIC.length];
            dis.readFully(b);
            if (!Arrays.equals(b, MAGIC)) {
                logger.warning("Wrong magic word");
                throw new RuntimeException();
            }
            int version = dis.readInt();
            if (version > VERSION) {
                logger.warning("Playlist has newer version, expected: " + VERSION + " got: " + version);
                throw new RuntimeException();
            }
            int size = dis.readInt();
            ensureCapacity(size);
            for (int i = 0; i < size; i++) {
                Track track = new Track();
                TrackData trackData = track.getTrackData();
                trackData.setLocation(dis.readUTF());
                trackData.setStartPosition(dis.readLong());
                trackData.setTotalSamples(dis.readLong());
                trackData.setSubsongIndex(dis.readInt());
                cache.cache(track);
                trackData = track.getTrackData();
                if (trackData.getSubsongIndex() > 0) {
                    trackData.setCueEmbedded(dis.readBoolean());
                    if (!trackData.isCueEmbedded())
                        trackData.setCueLocation(dis.readUTF());
                }
                trackData.setBps(dis.readInt());
                trackData.setChannels(dis.readInt());
                trackData.setSampleRate(dis.readInt());
                trackData.setBitrate(dis.readInt());
                if (version == 1) {
                    trackData.setDateAdded(System.currentTimeMillis());
                } else {
                    trackData.setDateAdded(dis.readLong());
                    trackData.setLastModified(dis.readLong());
                }
                int metaSize = dis.readInt();

                for (int j = 0; j < metaSize; j++) {
                    String key = dis.readUTF();
                    String value = dis.readUTF();
                    if (version == VERSION) {
                      if (key.equals(META_KEY_CODEC)) {
                          trackData.setCodec(value);
                      }
                      else if (key.equals(META_KEY_ENCODER)) {
                          trackData.setEncoder(value);
                      }
                    else {
                          trackData.addTagFieldValues(FieldKey.valueOf(key), value);
                      }
                    }
                    // read older playlist version
                    else {
                      if (key.equals(META_KEY_CODEC)) {
                          trackData.setCodec(value);
                      }
                      else if (key.equals(OLD_META_KEY_ARTIST)) {
                          trackData.addArtist(value);
                      }
                      else if (key.equals(OLD_META_KEY_ALBUM)) {
                          trackData.addAlbum(value);
                      }
                      else if (key.equals(OLD_META_KEY_ALBUM_ARTIST)) {
                          trackData.addAlbumArtist(value);
                      }
                      else if (key.equals(OLD_META_KEY_TITLE)) {
                          trackData.addTitle(value);
                      }
                      else if (key.equals(OLD_META_KEY_TRACK_NUMBER)) {
                          trackData.addTrack(value);
                      }
                      else if (key.equals(OLD_META_KEY_TOTAL_TRACKS)) {
                          trackData.addTrackTotal(value);
                      }
                      else if (key.equals(OLD_META_KEY_DISC_NUMBER)) {
                          trackData.addDisc(value);
                      }
                      else if (key.equals(OLD_META_KEY_TOTAL_DISCS)) {
                          trackData.addDiscTotal(value);
                      }
                      else if (key.equals(OLD_META_KEY_YEAR)) {
                          trackData.addYear(value);
                      }
                      else if (key.equals(OLD_META_KEY_GENRE)) {
                          trackData.addGenre(value);
                      }
                      else if (key.equals(OLD_META_KEY_COMMENT)) {
                          trackData.addComment(value);
                      }
                    }
                }

                add(track);
            }

            dis.close();
        } catch (Exception e) {
            logger.warning("Failed to load playlist " + file.getName() + ": " + e.getMessage());
        }
    }

    public boolean isLibraryView() {
        return libraryView;
    }

    public void setLibraryView(boolean libraryView) {
        this.libraryView = libraryView;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGroupBy() {
        return groupBy;
    }

    public List<String> loadM3U(String location) {
        Scanner fi;
        ArrayList<String> items = new ArrayList<String>();
        logger.fine("Loading M3U from: " + location);
        try {
            File parent = null;
            if (location.toLowerCase().startsWith("http://")) {
                fi = new Scanner(new URL(location).openStream());
            } else {
                File source = new File(location);
                fi = new Scanner(source);
                parent = source.getParentFile().getAbsoluteFile();
            }

            while (fi.hasNextLine()) {
                String line = fi.nextLine().trim();
                if (line.isEmpty() || line.startsWith("#"))
                    continue;
                // skip utf8 BOM
                if (((int) line.charAt(0)) == 0xFEFF) {
                    line = line.substring(1);
                }

                if (line.toLowerCase().startsWith("http://")) {
                    items.add(line);
                } else {
                    //it's a file, resolve it
                    File file = new File(line);
                    if (!file.isAbsolute())
                        file = new File(parent, line);
                    items.add(file.getAbsolutePath());
                }
            }
            fi.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return items;
    }

    public void saveM3U(File file) {
        try {
            PrintWriter pw = new PrintWriter(file);
            pw.println("#EXTM3U");
            Expression expression = Parser.parse("[%artist% - ]%title%");
            for (Track track : this) {
                if (track.getTrackData().isStream()) {
                    pw.println(track.getTrackData().getLocation());
                } else if (track.getTrackData().isFile()) {
                    int seconds = (int) AudioMath.samplesToMillis(
                            track.getTrackData().getTotalSamples(),
                            track.getTrackData().getSampleRate()) / 1000;
                    String title = String.valueOf(expression.eval(track));
                    pw.printf("#EXTINF:%d,%s\n%s\n",
                            seconds, title,
                            track.getTrackData().getFile().getAbsolutePath());
                }
            }
            pw.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    public List<String> loadPLS(String location) {
        Scanner fi;
        ArrayList<String> items = new ArrayList<String>();
        logger.fine("Loading PLS from: " + location);

        try {
            if (location.toLowerCase().startsWith("http://")) {
                fi = new Scanner(new URL(location).openStream());
            } else {
                fi = new Scanner(new File(location));
            }

            if (!fi.nextLine().equalsIgnoreCase("[playlist]")) {
                logger.warning("PLS has to start with [playlist]: " + location);
                return items;
            }

            fi.useDelimiter("[=\\p{javaWhitespace}+]");
            while (fi.hasNext()) {
                String line = fi.next().trim();
                if (line.toLowerCase().startsWith("file")) {
                    items.add(fi.next());
                }
            }
            fi.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return items;
    }

    public void savePLS(File file) {
        try {
            PrintWriter pw = new PrintWriter(file);
            Expression expression = Parser.parse("[%artist% - ]%title%");
            pw.println("[playlist]");
            pw.println("NumberOfEntries=" + size());
            for (int i = 0; i < size(); i++) {
                Track track = get(i);
                int index = i + 1;
                if (track.getTrackData().isFile()) {
                    pw.printf("File%d=%s\n", index, track.getTrackData().getFile().getAbsolutePath());
                    pw.printf("Title%d=%s\n", index, expression.eval(track));
                    pw.printf("Length%d=%s\n", index, (int) AudioMath.samplesToMillis(
                            track.getTrackData().getTotalSamples(),
                            track.getTrackData().getSampleRate()) / 1000);
                } else if (track.getTrackData().isStream()) {
                    pw.printf("File%d=%s\n", index, track.getTrackData().getLocation().normalize());
                }
                pw.println();
            }
            pw.println("Version=2");
            pw.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    public int insertItem(String address, int location, boolean recurse, Map<String, Object> progress) {
        ArrayList<Track> temp = new ArrayList<Track>();
        LinkedList<Object> queue = new LinkedList<Object>();

        if (location == -1)
            location = size();
        String ext = Util.getFileExt(address);
        if (ext.equals("m3u") || ext.equals("m3u8")) {
            queue.addAll(loadM3U(address));
        } else if (ext.equals("pls")) {
            queue.addAll(loadPLS(address));
        } else if (ext.equals("mus")) {
            Playlist newPl = new Playlist();
            newPl.load(new File(address));
            addAll(location, newPl);
        } else {
            queue.push(address);
        }

        while (!queue.isEmpty()) {
            try {
                Object top = queue.pop();
                String topStr = top.toString();
                if (progress != null) {
                    if (progress.get("processing.stop") != null) {
                        break;
                    }
                    progress.put("processing.file", topStr);
                }

                if (top instanceof String && topStr.startsWith("http://")) {
                    Track track = new Track();

                    URI uri = new URI(topStr);
                    String title = uri.getPath();
                    if (Util.isEmpty(title))
                        title = uri.getHost();
                    track.getTrackData().addTitle(title);
                    track.getTrackData().setLocation(uri.toString());
                    track.getTrackData().setTotalSamples(-1);
                    temp.add(track);
                } else {
                    File file = null;
                    if (top instanceof String)
                        file = new File(topStr);
                    else if (top instanceof File)
                        file = (File) top;

                    if (recurse && file.isDirectory()) {
                        queue.addAll(0, Arrays.asList(file.listFiles()));
                    } else if (file.isFile()) {
                        AudioFileReader reader = TrackIO.getAudioFileReader(file.getName());
                        if (reader != null) {
                            if (!Util.getFileExt(file).equals("cue")) {
                                String name = Util.removeExt(file.getAbsolutePath()) + ".cue";
                                if (new File(name).exists()) {
                                    continue;
                                }
                            }
                            reader.read(file, temp);
                        }
                    }
                }
            } catch (Exception ignored) {
            }
        }

        Collections.sort(temp, new Comparator<Track>() {
            @Override
            public int compare(Track o1, Track o2) {
                return o1.getTrackData().getLocation().compareTo(o2.getTrackData().getLocation());
            }
        });
        TrackDataCache cache = TrackDataCache.getInstance();
        for (Track track : temp) {
            cache.cache(track);
        }
        int sizeOld = size();
        addAll(location, temp);
        firePlaylistChanged();
        queue.clear();
        temp.clear();
        return size() - sizeOld;
    }

    public void sort(String expression, boolean toggle) {
        logger.fine("Sorting playlist with expression: " + expression);
        if (toggle && expression.equals(sortBy)) {
            sortAscending = !sortAscending;
        } else {
            sortAscending = true;
            sortBy = expression;
        }

        final Expression e = Parser.parse(expression);
        TrackComparator trackComparator = new TrackComparator(e);
        if (sortAscending)
            Collections.sort(this, trackComparator);
        else
            Collections.sort(this, Collections.reverseOrder(trackComparator));
    }

    public void setGroupBy(String expression) {
        groupBy = expression;
        logger.fine("Grouping playlist with expression: " + expression);
        groupExpression = Util.isEmpty(expression) ? null : Parser.parse(expression);

        firePlaylistChanged();
    }

    public void firePlaylistChanged() {
        regroup();
        for (PlaylistListener listener : listeners) {
            listener.playlistUpdated(this);
        }
    }

    public void regroup() {
        cleanUp();

        if (groupExpression == null)
            return;

        int start = 0;
        int size = 0;
        final String unknown = "?";
        String groupName = null;
        for (int i = 0; i < size(); i++) {
            Track track = get(i);
            Object o = groupExpression.eval(track);
            String value = null;
            if (o != null)
                value = o.toString();

            if (Util.isEmpty(value))
                value = unknown;

            if (groupName == null) {
                groupName = value;
                start = i;
                size = 1;
                continue;
            }

            //noinspection ConstantConditions
            boolean sameGroup = value.equalsIgnoreCase(groupName);
            if (sameGroup)
                size++;

            if (!sameGroup) {
                if (size > 0) {
                    addGroup(groupName, start, size);
                } else {
                    i--;
                }

                groupName = null;
            }
        }

        if (groupName != null)
            addGroup(groupName, start, size);
    }

    private void addGroup(String groupName, int start, int size) {
        SeparatorTrack group = new SeparatorTrack(groupName, size);
        add(start, group);
    }

    @Override
    public Track get(int index) {
        return index >= 0 && index < size() ? super.get(index) : null;
    }

    @Override
    public String toString() {
        if (groupBy == null)
            groupBy = "";
        return format.format(new Object[]{name, groupBy, libraryView});
    }

    @Override
    public boolean equals(Object o) {
        return o instanceof Playlist && this == o;
    }

    public void removeDeadItems() {
        for (Iterator it = this.iterator(); it.hasNext();) {
            Track track = (Track) it.next();
            if (track.getTrackData().getLocation() == null)
                continue;
            if (track.getTrackData().isFile() && !track.getTrackData().getFile().exists()) {
                it.remove();
            }
        }
        firePlaylistChanged();
    }

    public void removeDuplicates() {
        ArrayList<Track> dup = new ArrayList<Track>();
        for (int i = 0; i < size() - 1; i++) {
            Track t1 = get(i);
            URI l1 = t1.getTrackData().getLocation();
            if (l1 == null)
                continue;
            for (int j = i + 1; j < size(); j++) {
                Track t2 = get(j);

                if (l1.equals(t2.getTrackData().getLocation()) &&
                        t1.getTrackData().getSubsongIndex() == t2.getTrackData().getSubsongIndex()) {
                    dup.add(t2);
                }
            }
        }

        removeAll(dup);
        firePlaylistChanged();
    }

    public void addChangeListener(PlaylistListener listener) {
        listeners.add(listener);
    }

    public void removeChangeListener(PlaylistListener listener) {
        listeners.remove(listener);
    }

    public void setColumns(List<PlaylistColumn> columns) {
        this.columns = columns;
    }

    public List<PlaylistColumn> getColumns() {
        return columns;
    }

}
TOP

Related Classes of com.tulskiy.musique.playlist.Playlist

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.