Merge branch 'Orycteropus-feature/artist_mosaic'
This commit is contained in:
commit
f84e77781e
10 changed files with 304 additions and 167 deletions
|
|
@ -4,6 +4,9 @@ import android.content.Context;
|
|||
import android.graphics.Bitmap;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.bumptech.glide.BitmapRequestBuilder;
|
||||
import com.bumptech.glide.DrawableRequestBuilder;
|
||||
import com.bumptech.glide.DrawableTypeRequest;
|
||||
|
|
@ -15,10 +18,13 @@ import com.bumptech.glide.load.resource.drawable.GlideDrawable;
|
|||
import com.bumptech.glide.request.target.Target;
|
||||
import com.kabouzeid.gramophone.App;
|
||||
import com.kabouzeid.gramophone.R;
|
||||
import com.kabouzeid.gramophone.glide.artistimage.AlbumCover;
|
||||
import com.kabouzeid.gramophone.glide.artistimage.ArtistImage;
|
||||
import com.kabouzeid.gramophone.glide.palette.BitmapPaletteTranscoder;
|
||||
import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper;
|
||||
import com.kabouzeid.gramophone.model.Album;
|
||||
import com.kabouzeid.gramophone.model.Artist;
|
||||
import com.kabouzeid.gramophone.model.Song;
|
||||
import com.kabouzeid.gramophone.util.ArtistSignatureUtil;
|
||||
import com.kabouzeid.gramophone.util.CustomArtistImageUtil;
|
||||
|
||||
|
|
@ -27,7 +33,7 @@ import com.kabouzeid.gramophone.util.CustomArtistImageUtil;
|
|||
*/
|
||||
public class ArtistGlideRequest {
|
||||
|
||||
private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.SOURCE;
|
||||
private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL;
|
||||
private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_artist_image;
|
||||
public static final int DEFAULT_ANIMATION = android.R.anim.fade_in;
|
||||
|
||||
|
|
@ -35,7 +41,6 @@ public class ArtistGlideRequest {
|
|||
final RequestManager requestManager;
|
||||
final Artist artist;
|
||||
boolean noCustomImage;
|
||||
boolean forceDownload;
|
||||
|
||||
public static Builder from(@NonNull RequestManager requestManager, Artist artist) {
|
||||
return new Builder(requestManager, artist);
|
||||
|
|
@ -59,14 +64,9 @@ public class ArtistGlideRequest {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder forceDownload(boolean forceDownload) {
|
||||
this.forceDownload = forceDownload;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DrawableRequestBuilder<GlideDrawable> build() {
|
||||
//noinspection unchecked
|
||||
return createBaseRequest(requestManager, artist, noCustomImage, forceDownload)
|
||||
return createBaseRequest(requestManager, artist, noCustomImage)
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.error(DEFAULT_ERROR_IMAGE)
|
||||
.animate(DEFAULT_ANIMATION)
|
||||
|
|
@ -85,7 +85,7 @@ public class ArtistGlideRequest {
|
|||
|
||||
public BitmapRequestBuilder<?, Bitmap> build() {
|
||||
//noinspection unchecked
|
||||
return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload)
|
||||
return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage)
|
||||
.asBitmap()
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.error(DEFAULT_ERROR_IMAGE)
|
||||
|
|
@ -107,7 +107,7 @@ public class ArtistGlideRequest {
|
|||
|
||||
public BitmapRequestBuilder<?, BitmapPaletteWrapper> build() {
|
||||
//noinspection unchecked
|
||||
return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload)
|
||||
return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage)
|
||||
.asBitmap()
|
||||
.transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class)
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
|
|
@ -119,16 +119,21 @@ public class ArtistGlideRequest {
|
|||
}
|
||||
}
|
||||
|
||||
public static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Artist artist, boolean noCustomImage, boolean forceDownload) {
|
||||
public static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Artist artist, boolean noCustomImage) {
|
||||
boolean hasCustomImage = CustomArtistImageUtil.getInstance(App.getInstance()).hasCustomArtistImage(artist);
|
||||
if (noCustomImage || !hasCustomImage) {
|
||||
return requestManager.load(new ArtistImage(artist.getName(), forceDownload));
|
||||
final List<AlbumCover> songs = new ArrayList<>();
|
||||
for (final Album album : artist.albums) {
|
||||
final Song song = album.safeGetFirstSong();
|
||||
songs.add(new AlbumCover(album.getYear(), song.data));
|
||||
}
|
||||
return requestManager.load(new ArtistImage(artist.getName(), songs));
|
||||
} else {
|
||||
return requestManager.load(CustomArtistImageUtil.getFile(artist));
|
||||
}
|
||||
}
|
||||
|
||||
public static Key createSignature(Artist artist) {
|
||||
private static Key createSignature(Artist artist) {
|
||||
return ArtistSignatureUtil.getInstance(App.getInstance()).getArtistSignature(artist.getName());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package com.kabouzeid.gramophone.glide;
|
|||
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.GlideBuilder;
|
||||
import com.bumptech.glide.module.GlideModule;
|
||||
|
|
@ -10,8 +12,6 @@ import com.kabouzeid.gramophone.glide.artistimage.ArtistImageLoader;
|
|||
import com.kabouzeid.gramophone.glide.audiocover.AudioFileCover;
|
||||
import com.kabouzeid.gramophone.glide.audiocover.AudioFileCoverLoader;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
|
|
@ -24,6 +24,6 @@ public class PhonographGlideModule implements GlideModule {
|
|||
@Override
|
||||
public void registerComponents(Context context, Glide glide) {
|
||||
glide.register(AudioFileCover.class, InputStream.class, new AudioFileCoverLoader.Factory());
|
||||
glide.register(ArtistImage.class, InputStream.class, new ArtistImageLoader.Factory(context));
|
||||
glide.register(ArtistImage.class, InputStream.class, new ArtistImageLoader.Factory());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
package com.kabouzeid.gramophone.glide.artistimage;
|
||||
|
||||
/**
|
||||
* Used to define the artist cover
|
||||
*/
|
||||
public class AlbumCover {
|
||||
|
||||
private int year;
|
||||
|
||||
private String filePath;
|
||||
|
||||
public AlbumCover(int year, String filePath) {
|
||||
|
||||
this.filePath = filePath;
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public int getYear() {
|
||||
|
||||
return year;
|
||||
}
|
||||
|
||||
public void setYear(int year) {
|
||||
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public String getFilePath() {
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
|
||||
this.filePath = filePath;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,27 @@
|
|||
package com.kabouzeid.gramophone.glide.artistimage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public class ArtistImage {
|
||||
public final String artistName;
|
||||
public final boolean skipOkHttpCache;
|
||||
|
||||
public ArtistImage(String artistName, boolean skipOkHttpCache) {
|
||||
// filePath to get the image of the artist
|
||||
public final List<AlbumCover> albumCovers;
|
||||
|
||||
public ArtistImage(String artistName, final List<AlbumCover> albumCovers) {
|
||||
|
||||
this.artistName = artistName;
|
||||
this.skipOkHttpCache = skipOkHttpCache;
|
||||
this.albumCovers = albumCovers;
|
||||
}
|
||||
|
||||
public String toIdString() {
|
||||
StringBuilder id = new StringBuilder(artistName);
|
||||
for (AlbumCover albumCover: albumCovers) {
|
||||
id.append(albumCover.getYear()).append(albumCover.getFilePath());
|
||||
}
|
||||
return id.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,84 +1,175 @@
|
|||
package com.kabouzeid.gramophone.glide.artistimage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.load.data.DataFetcher;
|
||||
import com.bumptech.glide.load.model.GlideUrl;
|
||||
import com.bumptech.glide.load.model.ModelLoader;
|
||||
import com.kabouzeid.gramophone.lastfm.rest.LastFMRestClient;
|
||||
import com.kabouzeid.gramophone.lastfm.rest.model.LastFmArtist;
|
||||
import com.kabouzeid.gramophone.util.LastFMUtil;
|
||||
import com.kabouzeid.gramophone.util.MusicUtil;
|
||||
import com.kabouzeid.gramophone.glide.audiocover.AudioFileCoverUtils;
|
||||
import com.kabouzeid.gramophone.util.ImageUtil;
|
||||
import com.kabouzeid.gramophone.util.PreferenceUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import retrofit2.Response;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public class ArtistImageFetcher implements DataFetcher<InputStream> {
|
||||
|
||||
private Context context;
|
||||
private final LastFMRestClient lastFMRestClient;
|
||||
private final ArtistImage model;
|
||||
private ModelLoader<GlideUrl, InputStream> urlLoader;
|
||||
private final int width;
|
||||
private final int height;
|
||||
private volatile boolean isCancelled;
|
||||
private DataFetcher<InputStream> urlFetcher;
|
||||
|
||||
public ArtistImageFetcher(Context context, LastFMRestClient lastFMRestClient, ArtistImage model, ModelLoader<GlideUrl, InputStream> urlLoader, int width, int height) {
|
||||
this.context = context;
|
||||
this.lastFMRestClient = lastFMRestClient;
|
||||
private InputStream stream;
|
||||
|
||||
private boolean ignoreMediaStore;
|
||||
|
||||
public ArtistImageFetcher(final ArtistImage model, boolean ignoreMediaStore) {
|
||||
this.model = model;
|
||||
this.urlLoader = urlLoader;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.ignoreMediaStore = ignoreMediaStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
// makes sure we never ever return null here
|
||||
return String.valueOf(model.artistName);
|
||||
Log.d("MOSAIC", "get id for" + model.artistName);
|
||||
// never return NULL here!
|
||||
// this id is used to determine whether the image is already cached
|
||||
// we use the artist name as well as the album years + file paths
|
||||
return model.toIdString() + "ignoremediastore:" + ignoreMediaStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream loadData(Priority priority) throws Exception {
|
||||
if (!MusicUtil.isArtistNameUnknown(model.artistName) && PreferenceUtil.isAllowedToDownloadMetadata(context)) {
|
||||
Response<LastFmArtist> response = lastFMRestClient.getApiService().getArtistInfo(model.artistName, null, model.skipOkHttpCache ? "no-cache" : null).execute();
|
||||
Log.d("MOSAIC", "load data for" + model.artistName);
|
||||
return stream = getMosaic(model.albumCovers);
|
||||
}
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IOException("Request failed with code: " + response.code());
|
||||
private InputStream getMosaic(final List<AlbumCover> albumCovers) throws FileNotFoundException {
|
||||
|
||||
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
||||
|
||||
int artistBitMapSize = 512;
|
||||
|
||||
final Map<InputStream, Integer> images = new HashMap<>();
|
||||
|
||||
InputStream result = null;
|
||||
List<InputStream> streams = new ArrayList<>();
|
||||
|
||||
try {
|
||||
for (final AlbumCover cover : albumCovers) {
|
||||
byte[] picture = null;
|
||||
if (!ignoreMediaStore) {
|
||||
retriever.setDataSource(cover.getFilePath());
|
||||
picture = retriever.getEmbeddedPicture();
|
||||
}
|
||||
final InputStream stream;
|
||||
if (picture != null) {
|
||||
stream = new ByteArrayInputStream(picture);
|
||||
} else {
|
||||
stream = AudioFileCoverUtils.fallback(cover.getFilePath());
|
||||
}
|
||||
|
||||
if (stream != null) {
|
||||
images.put(stream, cover.getYear());
|
||||
}
|
||||
}
|
||||
|
||||
LastFmArtist lastFmArtist = response.body();
|
||||
int nbImages = images.size();
|
||||
|
||||
if (isCancelled) return null;
|
||||
if (nbImages > 3) {
|
||||
streams = new ArrayList<>(images.keySet());
|
||||
|
||||
GlideUrl url = new GlideUrl(LastFMUtil.getLargestArtistImageUrl(lastFmArtist.getArtist().getImage()));
|
||||
urlFetcher = urlLoader.getResourceFetcher(url, width, height);
|
||||
int divisor = 1;
|
||||
for (int i = 1; i < nbImages && Math.pow(i, 2) <= nbImages; ++i) {
|
||||
divisor = i;
|
||||
}
|
||||
divisor += 1;
|
||||
double nbTiles = Math.pow(divisor, 2);
|
||||
|
||||
if (nbImages < nbTiles) {
|
||||
divisor -= 1;
|
||||
nbTiles = Math.pow(divisor, 2);
|
||||
}
|
||||
final int resize = (artistBitMapSize / divisor) + 1;
|
||||
|
||||
final Bitmap bitmap = Bitmap.createBitmap(artistBitMapSize, artistBitMapSize, Bitmap.Config.RGB_565);
|
||||
final Canvas canvas = new Canvas(bitmap);
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
for (int i = 0; i < streams.size() && i < nbTiles; ++i) {
|
||||
final Bitmap bitmap1 = ImageUtil.resize(streams.get(i), resize, resize);
|
||||
canvas.drawBitmap(bitmap1, x, y, null);
|
||||
x += resize;
|
||||
|
||||
if (x >= artistBitMapSize) {
|
||||
x = 0;
|
||||
y += resize;
|
||||
}
|
||||
}
|
||||
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos);
|
||||
result = new ByteArrayInputStream(bos.toByteArray());
|
||||
|
||||
} else if (nbImages > 0) {
|
||||
// we return the last cover album of the artist
|
||||
Map.Entry<InputStream, Integer> maxEntryYear = null;
|
||||
|
||||
for (final Map.Entry<InputStream, Integer> entry : images.entrySet()) {
|
||||
if (maxEntryYear == null || entry.getValue()
|
||||
.compareTo(maxEntryYear.getValue()) > 0) {
|
||||
maxEntryYear = entry;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxEntryYear != null) {
|
||||
result = maxEntryYear.getKey();
|
||||
} else {
|
||||
result = images.entrySet()
|
||||
.iterator()
|
||||
.next()
|
||||
.getKey();
|
||||
}
|
||||
|
||||
}
|
||||
} finally {
|
||||
retriever.release();
|
||||
try {
|
||||
for (final InputStream stream : streams) {
|
||||
stream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return urlFetcher.loadData(priority);
|
||||
}
|
||||
return null;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
if (urlFetcher != null) {
|
||||
urlFetcher.cleanup();
|
||||
// already cleaned up in loadData and ByteArrayInputStream will be GC'd
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException ignore) {
|
||||
// can't do much about it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
isCancelled = true;
|
||||
if (urlFetcher != null) {
|
||||
urlFetcher.cancel();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,68 +2,42 @@ package com.kabouzeid.gramophone.glide.artistimage;
|
|||
|
||||
import android.content.Context;
|
||||
|
||||
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.bumptech.glide.load.data.DataFetcher;
|
||||
import com.bumptech.glide.load.model.GenericLoaderFactory;
|
||||
import com.bumptech.glide.load.model.GlideUrl;
|
||||
import com.bumptech.glide.load.model.ModelLoader;
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory;
|
||||
import com.bumptech.glide.load.model.stream.StreamModelLoader;
|
||||
import com.kabouzeid.gramophone.lastfm.rest.LastFMRestClient;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import com.kabouzeid.gramophone.util.PreferenceUtil;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
|
||||
public class ArtistImageLoader implements StreamModelLoader<ArtistImage> {
|
||||
// we need these very low values to make sure our artist image loading calls doesn't block the image loading queue
|
||||
private static final int TIMEOUT = 700;
|
||||
|
||||
private Context context;
|
||||
private LastFMRestClient lastFMClient;
|
||||
private ModelLoader<GlideUrl, InputStream> urlLoader;
|
||||
|
||||
public ArtistImageLoader(Context context, LastFMRestClient lastFMRestClient, ModelLoader<GlideUrl, InputStream> urlLoader) {
|
||||
public ArtistImageLoader(Context context) {
|
||||
this.context = context;
|
||||
this.lastFMClient = lastFMRestClient;
|
||||
this.urlLoader = urlLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataFetcher<InputStream> getResourceFetcher(ArtistImage model, int width, int height) {
|
||||
return new ArtistImageFetcher(context, lastFMClient, model, urlLoader, width, height);
|
||||
public DataFetcher<InputStream> getResourceFetcher(final ArtistImage model, int width, int height) {
|
||||
|
||||
return new ArtistImageFetcher(model, PreferenceUtil.getInstance(context).ignoreMediaStoreArtwork());
|
||||
}
|
||||
|
||||
public static class Factory implements ModelLoaderFactory<ArtistImage, InputStream> {
|
||||
private LastFMRestClient lastFMClient;
|
||||
private OkHttpUrlLoader.Factory okHttpFactory;
|
||||
|
||||
public Factory(Context context) {
|
||||
okHttpFactory = new OkHttpUrlLoader.Factory(new OkHttpClient.Builder()
|
||||
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.build());
|
||||
lastFMClient = new LastFMRestClient(LastFMRestClient.createDefaultOkHttpClientBuilder(context)
|
||||
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelLoader<ArtistImage, InputStream> build(Context context, GenericLoaderFactory factories) {
|
||||
return new ArtistImageLoader(context, lastFMClient, okHttpFactory.build(context, factories));
|
||||
return new ArtistImageLoader(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teardown() {
|
||||
okHttpFactory.teardown();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,30 +2,23 @@ package com.kabouzeid.gramophone.glide.audiocover;
|
|||
|
||||
import android.media.MediaMetadataRetriever;
|
||||
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.load.data.DataFetcher;
|
||||
|
||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||
import org.jaudiotagger.audio.mp3.MP3File;
|
||||
import org.jaudiotagger.tag.TagException;
|
||||
import org.jaudiotagger.tag.images.Artwork;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.load.data.DataFetcher;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public class AudioFileCoverFetcher implements DataFetcher<InputStream> {
|
||||
private final AudioFileCover model;
|
||||
private FileInputStream stream;
|
||||
|
||||
private InputStream stream;
|
||||
|
||||
public AudioFileCoverFetcher(AudioFileCover model) {
|
||||
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
|
|
@ -36,50 +29,22 @@ public class AudioFileCoverFetcher implements DataFetcher<InputStream> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public InputStream loadData(Priority priority) throws Exception {
|
||||
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
||||
public InputStream loadData(final Priority priority) throws Exception {
|
||||
|
||||
final MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
||||
try {
|
||||
retriever.setDataSource(model.filePath);
|
||||
byte[] picture = retriever.getEmbeddedPicture();
|
||||
if (picture != null) {
|
||||
return new ByteArrayInputStream(picture);
|
||||
stream = new ByteArrayInputStream(picture);
|
||||
} else {
|
||||
return fallback(model.filePath);
|
||||
stream = AudioFileCoverUtils.fallback(model.filePath);
|
||||
}
|
||||
} finally {
|
||||
retriever.release();
|
||||
}
|
||||
}
|
||||
|
||||
private static final String[] FALLBACKS = {"cover.jpg", "album.jpg", "folder.jpg", "cover.png", "album.png", "folder.png"};
|
||||
|
||||
private InputStream fallback(String path) throws FileNotFoundException {
|
||||
// Method 1: use embedded high resolution album art if there is any
|
||||
try {
|
||||
MP3File mp3File = new MP3File(path);
|
||||
if (mp3File.hasID3v2Tag()) {
|
||||
Artwork art = mp3File.getTag().getFirstArtwork();
|
||||
if (art != null) {
|
||||
byte[] imageData = art.getBinaryData();
|
||||
return new ByteArrayInputStream(imageData);
|
||||
}
|
||||
}
|
||||
// If there are any exceptions, we ignore them and continue to the other fallback method
|
||||
} catch (ReadOnlyFileException ignored) {
|
||||
} catch (InvalidAudioFrameException ignored) {
|
||||
} catch (TagException ignored) {
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
// Method 2: look for album art in external files
|
||||
File parent = new File(path).getParentFile();
|
||||
for (String fallback : FALLBACKS) {
|
||||
File cover = new File(parent, fallback);
|
||||
if (cover.exists()) {
|
||||
return stream = new FileInputStream(cover);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.kabouzeid.gramophone.glide.audiocover;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||
import org.jaudiotagger.audio.mp3.MP3File;
|
||||
import org.jaudiotagger.tag.TagException;
|
||||
import org.jaudiotagger.tag.images.Artwork;
|
||||
|
||||
public class AudioFileCoverUtils {
|
||||
|
||||
public static final String[] FALLBACKS = {"cover.jpg", "album.jpg", "folder.jpg", "cover.png", "album.png", "folder.png"};
|
||||
|
||||
|
||||
public static InputStream fallback(String path) throws FileNotFoundException {
|
||||
// Method 1: use embedded high resolution album art if there is any
|
||||
try {
|
||||
MP3File mp3File = new MP3File(path);
|
||||
if (mp3File.hasID3v2Tag()) {
|
||||
Artwork art = mp3File.getTag().getFirstArtwork();
|
||||
if (art != null) {
|
||||
byte[] imageData = art.getBinaryData();
|
||||
return new ByteArrayInputStream(imageData);
|
||||
}
|
||||
}
|
||||
// If there are any exceptions, we ignore them and continue to the other fallback method
|
||||
} catch (ReadOnlyFileException ignored) {
|
||||
} catch (InvalidAudioFrameException ignored) {
|
||||
} catch (TagException ignored) {
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
// Method 2: look for album art in external files
|
||||
final File parent = new File(path).getParentFile();
|
||||
for (String fallback : FALLBACKS) {
|
||||
File cover = new File(parent, fallback);
|
||||
if (cover.exists()) {
|
||||
return new FileInputStream(cover);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,13 +4,6 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -20,6 +13,21 @@ import android.view.View;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.afollestad.materialcab.MaterialCab;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
|
|
@ -53,16 +61,6 @@ import com.kabouzeid.gramophone.util.NavigationUtil;
|
|||
import com.kabouzeid.gramophone.util.PhonographColorUtil;
|
||||
import com.kabouzeid.gramophone.util.PreferenceUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
/**
|
||||
* Be careful when changing things in this Activity!
|
||||
*/
|
||||
|
|
@ -113,7 +111,6 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement
|
|||
|
||||
private LastFMRestClient lastFMRestClient;
|
||||
|
||||
private boolean forceDownload;
|
||||
private final SimpleObservableScrollViewCallbacks observableScrollViewCallbacks = new SimpleObservableScrollViewCallbacks() {
|
||||
@Override
|
||||
public void onScrollChanged(int scrollY, boolean b, boolean b2) {
|
||||
|
|
@ -254,7 +251,6 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement
|
|||
|
||||
private void loadArtistImage() {
|
||||
ArtistGlideRequest.Builder.from(Glide.with(this), artist)
|
||||
.forceDownload(forceDownload)
|
||||
.generatePalette(this).build()
|
||||
.dontAnimate()
|
||||
.into(new PhonographColoredTarget(artistImage) {
|
||||
|
|
@ -263,7 +259,6 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement
|
|||
setColors(color);
|
||||
}
|
||||
});
|
||||
forceDownload = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -375,7 +370,6 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement
|
|||
case R.id.action_reset_artist_image:
|
||||
Toast.makeText(ArtistDetailActivity.this, getResources().getString(R.string.updating), Toast.LENGTH_SHORT).show();
|
||||
CustomArtistImageUtil.getInstance(ArtistDetailActivity.this).resetCustomArtistImage(artist);
|
||||
forceDownload = true;
|
||||
return true;
|
||||
case R.id.action_colored_footers:
|
||||
item.setChecked(!item.isChecked());
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
|
|
@ -14,6 +15,8 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.kabouzeid.appthemehelper.util.TintHelper;
|
||||
|
||||
/**
|
||||
|
|
@ -109,4 +112,10 @@ public class ImageUtil {
|
|||
a.recycle();
|
||||
return drawable;
|
||||
}
|
||||
|
||||
public static Bitmap resize(InputStream stream, int scaledWidth, int scaledHeight) {
|
||||
final Bitmap bitmap = BitmapFactory.decodeStream(stream);
|
||||
return Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue