/*
* Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner,
* Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain,
* Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter,
* Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann,
* Samuel Zweifel
*
* This file is part of Jukefox.
*
* Jukefox is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or any later version. Jukefox 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Jukefox. If not, see <http://www.gnu.org/licenses/>.
*/
package ch.ethz.dcg.jukefox.manager.libraryimport;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.provider.MediaStore;
import android.widget.Toast;
import ch.ethz.dcg.jukefox.commons.AndroidConstants;
import ch.ethz.dcg.jukefox.commons.Constants;
import ch.ethz.dcg.jukefox.commons.DataUnavailableException;
import ch.ethz.dcg.jukefox.commons.utils.Log;
import ch.ethz.dcg.jukefox.data.HttpHelper;
import ch.ethz.dcg.jukefox.model.AbstractCollectionModelManager;
import ch.ethz.dcg.jukefox.model.collection.AlbumStatus;
import ch.ethz.dcg.jukefox.model.collection.BaseAlbum;
import ch.ethz.dcg.jukefox.model.collection.BaseArtist;
import ch.ethz.dcg.jukefox.model.collection.BaseSong;
import ch.ethz.dcg.jukefox.model.collection.CompleteAlbum;
import ch.ethz.dcg.jukefox.model.libraryimport.ImportState;
import ch.ethz.dcg.jukefox.model.providers.SongProvider;
import ch.ethz.dcg.pancho3.R;
import ch.ethz.dcg.pancho3.commons.utils.AndroidUtils;
import ch.ethz.dcg.pancho3.model.JukefoxApplication;
public class AndroidAlbumCoverFetcherThread extends AbstractAlbumCoverFetcherThread {
private static final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
private ConnectivityManager cm;
private Bitmap emptyCoverBitmap;
private boolean useCoverFiles;
private Context context;
private SongProvider songProvider;
private ContentResolver contentResolver;
public AndroidAlbumCoverFetcherThread(AbstractCollectionModelManager collectionModelManager,
List<AlbumCoverFetcherListener> listeners, ImportState importState, Context context) {
super(collectionModelManager, listeners, importState);
emptyCoverBitmap = AndroidUtils.getBitmapFromResource(context.getResources(), R.drawable.d005_empty_cd, 512);
this.context = context;
// SettingsProvider settingsProvider =
// collectionModelManager.getSettingsProvider();
// useCoverFiles = settingsProvider.isUseAlbumArtFiles();
cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
songProvider = collectionModelManager.getSongProvider();
contentResolver = context.getContentResolver();
}
@Override
protected boolean isReadyToDownloadCovers() {
boolean ready = AndroidUtils.isSdCardOk();
if (!ready) {
Log.v(TAG, "Sd-card is not Ok. Cancelling album cover fetcher thread");
showSdcardNotAvailableToast();
}
return ready;
}
private void showSdcardNotAvailableToast() {
JukefoxApplication.getHandler().post(new Runnable() {
@Override
public void run() {
Toast toast = Toast.makeText(context, context.getString(R.string.cover_fetcher_no_sd),
Toast.LENGTH_LONG);
toast.show();
}
});
}
/**
*
* @param album
* Album for which the album art has to be fetched
* @return an Array with the paths to two files: 1. The high Resolution album art, 2. the low resolution album art
* @throws Exception
*/
@Override
protected AlbumFetcherResult getAlbumCovers(CompleteAlbum album) throws Exception {
AlbumFetcherResult result = null;
// Log.v(TAG, "Getting covers for: " + album.getName() + ", status: " +
// album.getAlbumCoverStatus());
if (album.getAlbumCoverStatus() != AlbumStatus.COVER_UNCHECKED
&& album.getAlbumCoverStatus() != AlbumStatus.WEB_ERROR) {
Log.w(TAG, "Album should only be handled in coverFetcher if status is UNCHECKED or WEB_ERROR!");
return null;
}
if (album.getAlbumCoverStatus() == AlbumStatus.COVER_UNCHECKED) {
// Check for cover file in directory
if (useCoverFiles) {
result = getAlbumCoverFromDirectory(album);
if (result != null) {
// Log.v(TAG, "Fetched Album Art from directory for album: "
// + album.getName());
return result;
}
}
// See if album image is already on the phone
result = getAlbumCoverFromMediaProvider(album);
if (result != null) {
Log.v(TAG, "Got album cover from media provider");
return result;
}
}
// Log.v(TAG, "checking for compilation albums (artist: "
// + album.getArtists().get(0).getName());
if (album.getArtists().get(0).getName().equals(JukefoxApplication.albumArtistAlias)) {
// compilation album (as explicitly grouped by jukefox, or TCMP tag)
Log.v(TAG, "compilation album detected");
result = getAlbumCoverFromWeb(album, true, false);
if (result != null) {
return result;
}
Log.v(TAG, "no last.fm cover found for album name/various artists");
if (album.getAlbumCoverStatus() == AlbumStatus.COVER_UNCHECKED) {
result = getAlbumCoverFromMediaProviderBySong(album);
if (result != null) {
return result;
}
}
Log.v(TAG, "no cover found for songs from media provider");
// result = getAlbumCoverFromWeb(album, false, true);
// if (result != null) {
// return result;
// }
// Log.v(TAG, "no last.fm cover found for songs from web");
} else {
// "normal" album
result = getAlbumCoverFromWeb(album, true, true);
if (result != null) {
return result;
}
}
if (album.getAlbumCoverStatus() == AlbumStatus.CREATED_DEFAULT_COVER) {
return null; // return nothing if album was already previously
// created
}
// Log.v(TAG, "Create Album Art for album: " + album.getName());
// Else create cover
return createEmptyAlbumCover(album, AlbumStatus.CREATED_DEFAULT_COVER);
}
private AlbumFetcherResult getAlbumCoverFromWeb(CompleteAlbum album, boolean requestByAlbum, boolean requestBySong) {
AlbumFetcherResult result = null;
// else: get cover from last.fm
try {
// Log.v(TAG, "Connection Manager: " + cm);
// Log.v(TAG, "Active Connection: " + cm.getActiveNetworkInfo());
if (cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnectedOrConnecting()) {
if (requestByAlbum) {
result = getAlbumCoverFromWeb(album);
if (result != null) {
return result;
}
}
if (requestBySong) {
result = getAlbumCoverFromWebBySong(album);
if (result != null) {
return result;
}
}
}
} catch (Exception e) {
if (HttpHelper.isNetworkException(e)) {
if (album.getAlbumCoverStatus() == AlbumStatus.WEB_ERROR) {
return null; // return nothing if status was web error
// before
} else {
return createEmptyAlbumCover(album, AlbumStatus.WEB_ERROR);
}
}
Log.w(TAG, e);
}
return null;
}
private AlbumFetcherResult getAlbumCoverFromDirectory(CompleteAlbum album) {
String path;
try {
path = otherDataProvider.getSongPath(album.getSongs().get(0));
} catch (DataUnavailableException e1) {
Log.w(TAG, e1);
return null;
}
File song = new File(path);
File songDir = song.getParentFile();
if (songDir == null) {
return null;
}
File[] fileList = songDir.listFiles(imageFilter);
if (fileList == null || fileList.length == 0) {
return null;
}
FileInputStream is = null;
try {
is = new FileInputStream(fileList[0]);
Bitmap bitmapHigh = AndroidUtils.getBitmapFromInputStream(is, AndroidConstants.COVER_SIZE_HIGH_RES * 2);
bitmapHigh = resizeBitmap(bitmapHigh, AndroidConstants.COVER_SIZE_HIGH_RES);
Bitmap bitmapLow = resizeBitmap(bitmapHigh, AndroidConstants.COVER_SIZE_LOW_RES);
int color = getColorFromBitmap(bitmapLow);
if (bitmapHigh == null) {
return null;
} else {
// String highResPath = fileList[0].getAbsolutePath();
String highResPath = getDefaultCoverPath(false, album.getId());
saveImage(bitmapHigh, highResPath);
String lowResPath = getDefaultCoverPath(true, album.getId());
saveImage(bitmapLow, lowResPath);
return new AlbumFetcherResult(highResPath, lowResPath, color, AlbumStatus.DIRECTORY_COVER, album
.getId());
}
} catch (Exception e) {
Log.w(TAG, e);
} finally {
try {
if (is != null) {
is.close();
}
} catch (Exception e) {
Log.w(TAG, e);
}
}
return null;
}
private int getColorFromBitmap(Bitmap bitmap) {
int p1 = bitmap.getPixel(25, 25);
int p2 = bitmap.getPixel(25, 75);
int p3 = bitmap.getPixel(75, 25);
int p4 = bitmap.getPixel(75, 75);
int p5 = bitmap.getPixel(50, 50);
int red = Color.red(p1) + Color.red(p2) + Color.red(p3) + Color.red(p4) + Color.red(p5);
red /= 5;
int green = Color.green(p1) + Color.green(p2) + Color.green(p3) + Color.green(p4) + Color.green(p5);
green /= 5;
int blue = Color.blue(p1) + Color.blue(p2) + Color.blue(p3) + Color.blue(p4) + Color.blue(p5);
blue /= 5;
return Color.argb(255, red, green, blue);
}
/**
* Resize a bitmap to targetResxtargetRex pixels
*
* @param bm
* bitmap to resize
* @param targetRes
* height and width of the new bitmap
* @return
*/
private Bitmap resizeBitmap(Bitmap bm, int targetRes) {
// scale the image for opengl
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = (float) targetRes / width;
float scaleHeight = (float) targetRes / height;
// scale matrix
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
if (resizedBitmap == null) {
Log.w(TAG, "ResizedBitmap is null");
}
return resizedBitmap;
}
private void saveImage(Bitmap bitmap, String fileName) throws CouldNotSaveCoverException {
try {
FileOutputStream stream = new FileOutputStream(fileName);
bitmap.compress(Bitmap.CompressFormat.JPEG, 85, stream);
bitmap.recycle();
stream.close();
} catch (Exception e) {
Log.w(TAG, e);
throw new CouldNotSaveCoverException(e.getMessage(), e);
}
}
private AlbumFetcherResult createEmptyAlbumCover(CompleteAlbum album, AlbumStatus status) {
try {
Bitmap bitmapHigh = emptyCoverBitmap.copy(Bitmap.Config.RGB_565, true);
int bitmapSize = bitmapHigh.getHeight();
float textHeight = bitmapSize / 10;
// Draw Artist name and album name on cover
drawTextOnBitmap(album, bitmapHigh, bitmapSize, textHeight);
Bitmap bitmapLow = resizeBitmap(bitmapHigh, AndroidConstants.COVER_SIZE_LOW_RES);
int color = getColorFromBitmap(bitmapLow);
String highResPath = getDefaultCoverPath(false, album.getId());
String lowResPath = getDefaultCoverPath(true, album.getId());
saveImage(bitmapLow, lowResPath);
saveImage(bitmapHigh, highResPath);
return new AlbumFetcherResult(highResPath, lowResPath, color, status, album.getId());
} catch (Exception e) {
Log.w(TAG, e);
return null;
}
}
private String getDefaultCoverPath(boolean lowRes, int albumId) {
if (lowRes) {
return JukefoxApplication.getDirectoryManager().getAlbumCoverDirectory().getAbsolutePath() + Constants.FS
+ "album" + albumId + "L.jpg";
}
// else: high res
return JukefoxApplication.getDirectoryManager().getAlbumCoverDirectory().getAbsolutePath() + Constants.FS
+ "album" + albumId + "H.jpg";
}
private void drawTextOnBitmap(CompleteAlbum album, Bitmap bitmapHigh, int bitmapSize, float textHeight) {
Canvas canvas = new Canvas(bitmapHigh);
canvas.drawARGB(0, 125, 125, 125);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setTypeface(Typeface.SERIF);
paint.setSubpixelText(true);
paint.setTextSize(textHeight);
paint.setAntiAlias(true);
String artist = album.getArtists().get(0).getName();
String shortenedText = new String(artist);
int textLength = artist.length();
if (textLength > 18) {
shortenedText = artist.substring(0, 15) + "...";
textLength = 18;
} else if (textLength < 8) {
while (shortenedText.length() < 8) {
shortenedText = " " + shortenedText + " ";
}
}
float pixelLength = paint.measureText(shortenedText);
paint.setTextSize(textHeight * (bitmapSize * 2 / 3) / pixelLength);
canvas.drawText(shortenedText, bitmapSize / 6, bitmapSize / 3, paint);
shortenedText = album.getName();
textLength = album.getName().length();
if (textLength > 18) {
shortenedText = album.getName().substring(0, 15) + "...";
textLength = 18;
} else if (textLength < 8) {
while (shortenedText.length() < 8) {
shortenedText = " " + shortenedText + " ";
}
}
paint.setTextSize(bitmapSize / 10f);
pixelLength = paint.measureText(shortenedText);
textHeight = textHeight * bitmapSize * 2f / 3f / pixelLength;
paint.setTextSize(textHeight);
canvas.drawText(shortenedText, bitmapSize / 6f, bitmapSize * 2 / 3f + textHeight, paint);
}
/**
* Try to fetch an album cover from the internal DB
*/
private AlbumFetcherResult getAlbumCoverFromMediaProvider(CompleteAlbum album) throws CouldNotSaveCoverException {
return getAlbumCoverFromMediaProvider(album.getId(), album.getArtists().get(0).getName(), album.getName());
}
private AlbumFetcherResult getAlbumCoverFromMediaProviderBySong(CompleteAlbum album)
throws CouldNotSaveCoverException {
int albumId = album.getId();
String albumName = album.getName();
for (BaseSong<BaseArtist, BaseAlbum> s : album.getSongs()) {
String artistName = s.getArtist().getName();
AlbumFetcherResult result = getAlbumCoverFromMediaProvider(albumId, artistName, albumName);
if (result != null) {
return result;
}
}
return null;
}
/**
* Try to fetch an album cover from the internal DB
*/
private AlbumFetcherResult getAlbumCoverFromMediaProvider(int albumId, String artist, String albumName)
throws CouldNotSaveCoverException {
String where = MediaStore.Audio.Media.ARTIST + " = ? AND " + MediaStore.Audio.Media.ALBUM + " = ?";
// Add in the filtering constraints
String[] keywords = new String[] { artist, albumName };
String[] cols = new String[] { MediaStore.Audio.Albums._ID, MediaStore.Audio.Albums.ALBUM_ART };
Cursor cur = null;
try {
cur = contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, cols, where, keywords, null);
if (cur.moveToFirst()) {
return extractBitmapsFromMediaProviderCursor(albumId, cur);
}
// Log.v("Didn't get cover from media provider", album.getArtists()
// .get(0).getName()
// + " - " + album.getName());
return null;
} catch (CouldNotSaveCoverException e) {
throw e;
} catch (Exception e) {
// Log.w(TAG, "error reading cover from media provider");
// Log.v(TAG, "Error reading cover from media provider: "
// + e.getMessage());
return null;
} finally {
if (cur != null) {
cur.close();
}
}
}
private AlbumFetcherResult extractBitmapsFromMediaProviderCursor(int albumId, Cursor cur) throws Exception {
int cpAlbumId = cur.getInt(0);
Uri uri = ContentUris.withAppendedId(sArtworkUri, cpAlbumId);
if (uri != null) {
InputStream in = null;
try {
in = contentResolver.openInputStream(uri);
Bitmap bitmapHigh = AndroidUtils.getBitmapFromInputStream(in, 2 * AndroidConstants.COVER_SIZE_HIGH_RES);
bitmapHigh = resizeBitmap(bitmapHigh, AndroidConstants.COVER_SIZE_HIGH_RES);
if (bitmapHigh == null) {
return null;
}
// Log.v("Got cover from phone DB", album.getArtists().get(0)
// .getName()
// + " - "
// + album.getName()
// + " "
// + cpAlbumId
// + " HResPath: " + highResPath);
Bitmap bitmapLow = resizeBitmap(bitmapHigh, AndroidConstants.COVER_SIZE_LOW_RES);
int color = getColorFromBitmap(bitmapLow);
String lowResPath = getDefaultCoverPath(true, albumId);
String highResPath = getDefaultCoverPath(false, albumId);
saveImage(bitmapHigh, highResPath);
saveImage(bitmapLow, lowResPath);
return new AlbumFetcherResult(highResPath, lowResPath, color, AlbumStatus.CONTENT_PROVIDER_COVER,
albumId);
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException ex) {
Log.w(TAG, ex);
}
}
}
return null;
}
private AlbumFetcherResult getAlbumCoverFromWeb(CompleteAlbum album) throws Exception {
String urlStr = "";
String albumName = URLEncoder.encode(album.getName());
Integer artistMeId = otherDataProvider.getMusicExplorerArtistId(album.getArtists().get(0));
if (artistMeId == null) {
// Log.d(TAG, "MusicExplorerId for artist "
// + album.getArtists().get(0).getName() + " is null");
return null;
}
// Get Cover art URL from our server by the album name
urlStr = String.format(Constants.FORMAT_IMAGE_URL_REQUEST_PER_ALBUM, Integer.toString(artistMeId), albumName);
AlbumFetcherResult result = getAlbumCoverFromWeb(album, urlStr);
// if (result != null) {
// return result;
// }
//
// // Get Cover Art from our server by the song title
// return getAlbumCoverFromWebBySong(album);
return result;
}
private AlbumFetcherResult getAlbumCoverFromWebBySong(CompleteAlbum album) throws Exception {
List<BaseSong<BaseArtist, BaseAlbum>> songs = album.getSongs();
AlbumFetcherResult result = null;
for (BaseSong<BaseArtist, BaseAlbum> song : songs) {
Integer songMeId = otherDataProvider.getMusicExplorerSongId(song);
if (songMeId == null) {
continue;
}
String urlStr = String.format(Constants.FORMAT_IMAGE_URL_REQUEST_PER_SONG, Integer.toString(songMeId));
result = getAlbumCoverFromWeb(album, urlStr);
if (result != null) {
return result;
}
}
return null;
}
private AlbumFetcherResult getAlbumCoverFromWeb(CompleteAlbum album, String urlStr) throws Exception {
InputStream in = null;
try {
String url = getAlbumCoverUrlFromJukefoxServer(urlStr);
// Log.d(TAG, urlStr + " resolved " + url);
if (url == null) {
return null;
}
try {
in = getAlbumCoverInputStreamFromUrl(url);
} catch (Exception e) {
Log.w(TAG, e);
informJukefoxServerAboutInvalidUrl();
throw e;
}
Bitmap bitmapHigh = AndroidUtils.getBitmapFromInputStream(in, 2 * AndroidConstants.COVER_SIZE_HIGH_RES);
if (bitmapHigh == null) {
// Log.d(TAG, "could not get bitmap at " + url);
return null;
}
bitmapHigh = resizeBitmap(bitmapHigh, AndroidConstants.COVER_SIZE_HIGH_RES);
Bitmap bitmapLow = resizeBitmap(bitmapHigh, AndroidConstants.COVER_SIZE_LOW_RES);
int color = getColorFromBitmap(bitmapLow);
String lowResPath = getDefaultCoverPath(true, album.getId());
String highResPath = getDefaultCoverPath(false, album.getId());
saveImage(bitmapLow, lowResPath);
saveImage(bitmapHigh, highResPath);
return new AlbumFetcherResult(highResPath, lowResPath, color, AlbumStatus.WEB_COVER, album.getId());
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e) {
Log.w(TAG, e);
}
}
}
// private AlbumFetcherResult saveImages(CompleteAlbum album, String
// highResPath,
// Bitmap bitmapHigh, Bitmap bitmapLow, int color)
// throws CouldNotSaveCoverException {
//
// try {
// String lowResPath = Constants.COVER_DIRECTORY + "/title"
// + album.getId() + "L.png";
// saveImage(album.getId(), bitmapLow, lowResPath);
// saveImage(album.getId(), bitmapHigh, highResPath);
//
// return new AlbumFetcherResult(highResPath, lowResPath, color,
// AlbumStatus.WEB_COVER);
// } catch (Exception e) {
// Log.w(TAG, e);
// throw new CouldNotSaveCoverException(e.getMessage(), e);
// }
// }
private void informJukefoxServerAboutInvalidUrl() {
// TODO Auto-generated method stub
}
private String getAlbumCoverUrlFromJukefoxServer(String urlStr) throws Exception {
InputStream is = null;
try {
HttpGet httpGet = new HttpGet(urlStr);
HttpResponse httpResp = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResp.getEntity();
is = httpEntity.getContent();
BufferedReader bufread = new BufferedReader(new InputStreamReader(is));
String flag = bufread.readLine();
if (!flag.equals("URL:")) {
throw new UnknownHostException("Could not contact jukefox server for album image url!");
}
String url = bufread.readLine();
if (!url.startsWith("http")) {
return null;
}
return url;
} finally {
try {
if (is != null) {
is.close();
}
} catch (Exception e) {
Log.w(TAG, e);
}
}
}
private InputStream getAlbumCoverInputStreamFromUrl(String url) throws Exception {
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResp = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResp.getEntity();
InputStream is = httpEntity.getContent();
return is;
}
}