/***************************************************************************** * AudioUtil.java ***************************************************************************** * Copyright © 2011-2012 VLC authors and VideoLAN * * This program 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 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ package org.videolan.vlc.gui.audio; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import org.videolan.libvlc.LibVlcUtil; import org.videolan.libvlc.Media; import org.videolan.vlc.BitmapCache; import org.videolan.vlc.MurmurHash; import org.videolan.vlc.Util; import org.videolan.vlc.VLCApplication; import android.annotation.SuppressLint; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.media.RingtoneManager; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; import android.util.Log; import android.widget.Toast; import org.tribler.triblersvod.gui.R; public class AudioUtil { public final static String TAG = "VLC/AudioUtil"; public static String CACHE_DIR = null; public static String ART_DIR = null; public static String COVER_DIR = null; public static String PLAYLIST_DIR = null; public static void setRingtone( Media song, Activity activity){ File newringtone = LibVlcUtil.URItoFile(song.getLocation()); if(!newringtone.exists()) { Toast.makeText(activity.getApplicationContext(),activity.getString(R.string.ringtone_error), Toast.LENGTH_SHORT).show(); return; } ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DATA, newringtone.getAbsolutePath()); values.put(MediaStore.MediaColumns.TITLE, song.getTitle()); values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/*"); values.put(MediaStore.Audio.Media.ARTIST, song.getArtist()); values.put(MediaStore.Audio.Media.IS_RINGTONE, true); values.put(MediaStore.Audio.Media.IS_NOTIFICATION, false); values.put(MediaStore.Audio.Media.IS_ALARM, false); values.put(MediaStore.Audio.Media.IS_MUSIC, false); Uri uri = MediaStore.Audio.Media.getContentUriForPath(newringtone.getAbsolutePath()); Uri newUri; try { activity.getContentResolver().delete(uri, MediaStore.MediaColumns.DATA + "=\"" + newringtone.getAbsolutePath() + "\"", null); newUri = activity.getContentResolver().insert(uri, values); RingtoneManager.setActualDefaultRingtoneUri( activity.getApplicationContext(), RingtoneManager.TYPE_RINGTONE, newUri ); } catch(Exception e) { Toast.makeText(activity.getApplicationContext(), activity.getString(R.string.ringtone_error), Toast.LENGTH_SHORT).show(); return; } Toast.makeText( activity.getApplicationContext(), activity.getString(R.string.ringtone_set, song.getTitle()), Toast.LENGTH_SHORT) .show(); } @SuppressLint("NewApi") public static void prepareCacheFolder(Context context) { if (Util.isFroyoOrLater() && Util.hasExternalStorage() && context.getExternalCacheDir() != null) CACHE_DIR = context.getExternalCacheDir().getPath(); else CACHE_DIR = Environment.getExternalStorageDirectory().getPath() + "/Android/data/" + context.getPackageName() + "/cache"; ART_DIR = CACHE_DIR + "/art/"; COVER_DIR = CACHE_DIR + "/covers/"; PLAYLIST_DIR = CACHE_DIR + "/playlists/"; for(String path : Arrays.asList(ART_DIR, COVER_DIR, PLAYLIST_DIR)) { File file = new File(path); if (!file.exists()) file.mkdirs(); } } public static void clearCacheFolder() { for(String path : Arrays.asList(ART_DIR, COVER_DIR, PLAYLIST_DIR)) { File file = new File(path); if (file.exists()) deleteContent(file, false); } } private static void deleteContent(File dir, boolean deleteDir) { if (dir.isDirectory()) { File[] files = dir.listFiles(); for (File file : files) { deleteContent(file, true); } } if (deleteDir) dir.delete(); } private static String getCoverFromMediaStore(Context context, Media media) { ContentResolver contentResolver = context.getContentResolver(); Uri uri = android.provider.MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI; Cursor cursor = contentResolver.query(uri, new String[] { MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ALBUM_ART }, MediaStore.Audio.Albums.ALBUM + " LIKE ?", new String[] { media.getAlbum() }, null); if (cursor == null) { // do nothing } else if (!cursor.moveToFirst()) { // do nothing cursor.close(); } else { int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Albums.ALBUM_ART); String albumArt = cursor.getString(titleColumn); cursor.close(); return albumArt; } return null; } private static String getCoverFromVlc(Context context, Media media) throws NoSuchAlgorithmException, UnsupportedEncodingException { String artworkURL = media.getArtworkURL(); if (artworkURL != null && artworkURL.startsWith("file://")) { return Uri.decode(artworkURL).replace("file://", ""); } else if(artworkURL != null && artworkURL.startsWith("attachment://")) { // Decode if the album art is embedded in the file String mArtist = media.getArtist(); String mAlbum = media.getAlbum(); /* Parse decoded attachment */ if( mArtist.length() == 0 || mAlbum.length() == 0 || mArtist.equals(VLCApplication.getAppContext().getString(R.string.unknown_artist)) || mAlbum.equals(VLCApplication.getAppContext().getString(R.string.unknown_album)) ) { /* If artist or album are missing, it was cached by title MD5 hash */ MessageDigest md = MessageDigest.getInstance("MD5"); byte[] binHash = md.digest((artworkURL + media.getTitle()).getBytes("UTF-8")); /* Convert binary hash to normal hash */ BigInteger hash = new BigInteger(1, binHash); String titleHash = hash.toString(16); while(titleHash.length() < 32) { titleHash = "0" + titleHash; } /* Use generated hash to find art */ artworkURL = CACHE_DIR + "/art/arturl/" + titleHash + "/art.png"; } else { /* Otherwise, it was cached by artist and album */ artworkURL = CACHE_DIR + "/art/artistalbum/" + mArtist + "/" + mAlbum + "/art.png"; } return artworkURL; } return null; } private static String getCoverFromFolder(Context context, Media media) { File f = LibVlcUtil.URItoFile(media.getLocation()); if (f != null && f.getParentFile() != null && f.getParentFile().listFiles() != null) for (File s : f.getParentFile().listFiles()) { if (s.getAbsolutePath().endsWith("png") || s.getAbsolutePath().endsWith("jpg")) return s.getAbsolutePath(); } return null; } @SuppressLint("NewApi") public synchronized static Bitmap getCover(Context context, Media media, int width) { String coverPath = null; Bitmap cover = null; String cachePath = null; if (width <= 0) { Log.e(TAG, "Invalid cover width requested"); return null; } // if external storage is not available, skip covers to prevent slow audio browsing if (!Util.hasExternalStorage()) return null; try { // try to load from cache int hash = MurmurHash.hash32(media.getArtist()+media.getAlbum()); cachePath = COVER_DIR + (hash >= 0 ? "" + hash : "m" + (-hash)) + "_" + width; // try to get the cover from the LRUCache first BitmapCache cache = BitmapCache.getInstance(); cover = cache.getBitmapFromMemCache(cachePath); if (cover != null) return cover; // try to get the cover from the storage cache File cacheFile = new File(cachePath); if (cacheFile != null && cacheFile.exists()) { if (cacheFile.length() > 0) coverPath = cachePath; else return null; } // try to get it from VLC if (coverPath == null || !cacheFile.exists()) coverPath = getCoverFromVlc(context, media); // no found yet, looking in folder if (coverPath == null || !(new File(coverPath)).exists()) coverPath = getCoverFromFolder(context, media); // try to get the cover from android MediaStore if (coverPath == null || !(new File(coverPath)).exists()) coverPath = getCoverFromMediaStore(context, media); // read (and scale?) the bitmap cover = readCoverBitmap(context, coverPath, width); // store cover into both cache writeBitmap(cover, cachePath); cache.addBitmapToMemCache(cachePath, cover); } catch (Exception e) { e.printStackTrace(); } return cover; } private static void writeBitmap(Bitmap bitmap, String path) throws IOException { OutputStream out = null; try { File file = new File(path); if (file.exists() && file.length() > 0) return; out = new BufferedOutputStream(new FileOutputStream(file), 4096); if (bitmap != null) bitmap.compress(CompressFormat.JPEG, 90, out); } catch (Exception e) { Log.e(TAG, "writeBitmap failed : "+ e.getMessage()); } finally { if (out != null) { out.close(); } } } private static Bitmap readCoverBitmap(Context context, String path, int dipWidth) { Bitmap cover = null; BitmapFactory.Options options = new BitmapFactory.Options(); int width = Util.convertDpToPx(dipWidth); /* Get the resolution of the bitmap without allocating the memory */ options.inJustDecodeBounds = true; BitmapFactory.decodeFile(path, options); if (options.outWidth > 0 && options.outHeight > 0) { options.inJustDecodeBounds = false; options.inSampleSize = 2; // Find the best decoding scale for the bitmap while( options.outWidth / options.inSampleSize > width) options.inSampleSize++; options.inSampleSize--; // Decode the file (with memory allocation this time) cover = BitmapFactory.decodeFile(path, options); if (cover != null && options.outWidth > width) { int height = (int) (width * options.outHeight / ((double) options.outWidth)); cover = Bitmap.createScaledBitmap(cover, width, height, false); } } return cover; } }