add metadata extraction wrapper
This commit is contained in:
parent
80744ae9cc
commit
2df519f0f6
@ -53,8 +53,8 @@ public class MediaLibrary {
|
||||
sBackend = new MediaLibraryBackend(context);
|
||||
|
||||
sScanner = new MediaScanner(sBackend);
|
||||
File dir = new File("/storage");
|
||||
// sScanner.startScan(dir);
|
||||
File dir = new File("/storage/emulated/0");
|
||||
sScanner.startScan(dir);
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
*
|
||||
* 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 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ch.blinkenlights.android.medialibrary;
|
||||
|
||||
import ch.blinkenlights.bastp.Bastp;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MediaMetadataExtractor extends HashMap<String, ArrayList<String>> {
|
||||
// Well known tags
|
||||
public final static String ALBUM = "ALBUM";
|
||||
public final static String ALBUMARTIST = "ALBUM_ARTIST";
|
||||
public final static String ARTIST = "ARTIST";
|
||||
public final static String BITRATE = "BITRATE";
|
||||
public final static String COMPOSER = "COMPOSER";
|
||||
public final static String DISC_COUNT = "DISC_COUNT";
|
||||
public final static String DISC_NUMBER = "DISC_NUMBER";
|
||||
public final static String DURATION = "DURATION";
|
||||
public final static String GENRE = "GENRE";
|
||||
public final static String TRACK_COUNT = "TRACK_COUNT";
|
||||
public final static String TRACK_NUMBER = "TRACK_NUM";
|
||||
public final static String TITLE = "TITLE";
|
||||
public final static String YEAR = "YEAR";
|
||||
|
||||
/**
|
||||
* Constructor for MediaMetadataExtractor
|
||||
*
|
||||
* @param path the path to scan
|
||||
*/
|
||||
MediaMetadataExtractor(String path) {
|
||||
extractMetadata(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first element matching this key, null on if not found
|
||||
*
|
||||
* @param key the key to look up
|
||||
* @return the value of the first entry, null if the key was not found
|
||||
*/
|
||||
public String getFirst(String key) {
|
||||
String result = null;
|
||||
if (containsKey(key))
|
||||
result = get(key).get(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to populate this instance with tags found in given path
|
||||
*
|
||||
* @param path the path to parse
|
||||
* @return void, but populates `this'
|
||||
*/
|
||||
private void extractMetadata(String path) {
|
||||
if (isEmpty() == false)
|
||||
throw new IllegalStateException("Expected to be called on a clean HashMap");
|
||||
|
||||
HashMap bastpTags = (new Bastp()).getTags(path);
|
||||
MediaMetadataRetriever mediaTags = new MediaMetadataRetriever();
|
||||
try {
|
||||
mediaTags.setDataSource(path);
|
||||
} catch (Exception e) { /* we will later just check the contents of mediaTags */ }
|
||||
|
||||
// Check if this is an useable audio file
|
||||
if (mediaTags.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO) == null ||
|
||||
mediaTags.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO) != null ||
|
||||
mediaTags.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) == null) {
|
||||
mediaTags.release();
|
||||
return;
|
||||
}
|
||||
|
||||
// Bastp can not read the duration and bitrates, so we always get it from the system
|
||||
ArrayList<String> duration = new ArrayList<String>(1);
|
||||
duration.add(mediaTags.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
|
||||
this.put(DURATION, duration);
|
||||
|
||||
ArrayList<String> bitrate = new ArrayList<String>(1);
|
||||
bitrate.add(mediaTags.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
|
||||
this.put(BITRATE, bitrate);
|
||||
|
||||
|
||||
// ...but we are using bastp for FLAC, OGG and OPUS as it handles them well
|
||||
// Everything else goes to the framework (such as pcm, m4a and mp3)
|
||||
String bastpType = (bastpTags.containsKey("type") ? (String)bastpTags.get("type") : "");
|
||||
switch (bastpType) {
|
||||
case "FLAC":
|
||||
case "OGG":
|
||||
case "OPUS":
|
||||
populateSelf(bastpTags);
|
||||
break;
|
||||
default:
|
||||
populateSelf(mediaTags);
|
||||
}
|
||||
|
||||
mediaTags.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates `this' with tags read from bastp
|
||||
*
|
||||
* @param bastp A hashmap as returned by bastp
|
||||
*/
|
||||
private void populateSelf(HashMap bastp) {
|
||||
// mapping between vorbiscomment -> constant
|
||||
String[] map = new String[]{ "TITLE", TITLE, "ARTIST", ARTIST, "ALBUM", ALBUM, "ALBUMARTIST", ALBUMARTIST, "COMPOSER", COMPOSER, "GENRE", GENRE,
|
||||
"TRACKNUMBER", TRACK_NUMBER, "TRACKTOTAL", TRACK_COUNT, "DISCNUMBER", DISC_NUMBER, "DISCTOTAL", DISC_COUNT,
|
||||
"YEAR", YEAR };
|
||||
for (int i=0; i<map.length; i+=2) {
|
||||
if (bastp.containsKey(map[i])) {
|
||||
ArrayList<String> tags = (ArrayList<String>)bastp.get(map[i]);
|
||||
put(map[i+1], tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates `this' with tags read from the MediaMetadataRetriever
|
||||
*
|
||||
* @param tags a MediaMetadataRetriever object
|
||||
*/
|
||||
private void populateSelf(MediaMetadataRetriever tags) {
|
||||
int[] mediaMap = new int[] { MediaMetadataRetriever.METADATA_KEY_TITLE, MediaMetadataRetriever.METADATA_KEY_ARTIST, MediaMetadataRetriever.METADATA_KEY_ALBUM,
|
||||
MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, MediaMetadataRetriever.METADATA_KEY_COMPOSER, MediaMetadataRetriever.METADATA_KEY_GENRE,
|
||||
MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, MediaMetadataRetriever.METADATA_KEY_YEAR };
|
||||
String[] selfMap = new String[]{ TITLE, ARTIST, ALBUM, ALBUMARTIST, COMPOSER, GENRE, TRACK_NUMBER, YEAR };
|
||||
for (int i=0; i<selfMap.length; i++) {
|
||||
String data = tags.extractMetadata(mediaMap[i]);
|
||||
if (data != null) {
|
||||
ArrayList<String> md = new ArrayList<String>(1);
|
||||
md.add(data);
|
||||
put(selfMap[i], md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -27,8 +27,8 @@ import android.os.Message;
|
||||
import android.os.Process;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
public class MediaScanner implements Handler.Callback {
|
||||
/**
|
||||
@ -105,90 +105,83 @@ public class MediaScanner implements Handler.Callback {
|
||||
String path = file.getAbsolutePath();
|
||||
long songId = MediaLibrary.hash63(path);
|
||||
|
||||
HashMap tags = (new Bastp()).getTags(path);
|
||||
if (tags.containsKey("type") == false)
|
||||
return; // no tags found
|
||||
|
||||
Log.v("VanillaMusic", "> Found mime "+((String)tags.get("type")));
|
||||
|
||||
if (mBackend.isSongExisting(songId)) {
|
||||
Log.v("VanillaMusic", "Skipping already known song with id "+songId);
|
||||
return;
|
||||
}
|
||||
|
||||
MediaMetadataRetriever data = new MediaMetadataRetriever();
|
||||
try {
|
||||
data.setDataSource(path);
|
||||
} catch (Exception e) {
|
||||
Log.w("VanillaMusic", "Failed to extract metadata from " + path);
|
||||
}
|
||||
MediaMetadataExtractor tags = new MediaMetadataExtractor(path);
|
||||
if (tags.isEmpty())
|
||||
return; // file does not contain audio data
|
||||
|
||||
String duration = data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
|
||||
if (duration == null)
|
||||
return; // not a supported media file!
|
||||
// Get tags which always must be set
|
||||
String title = tags.getFirst(MediaMetadataExtractor.TITLE);
|
||||
if (title == null)
|
||||
title = "Untitled";
|
||||
|
||||
if (data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO) == null)
|
||||
return; // no audio -> do not index
|
||||
String album = tags.getFirst(MediaMetadataExtractor.ALBUM);
|
||||
if (album == null)
|
||||
album = "No Album";
|
||||
|
||||
if (data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO) != null)
|
||||
return; // has a video stream -> do not index
|
||||
String artist = tags.getFirst(MediaMetadataExtractor.ARTIST);
|
||||
if (artist == null)
|
||||
artist = "No Artist";
|
||||
|
||||
String title = (tags.containsKey("TITLE") ? (String)((Vector)tags.get("TITLE")).get(0) : "Untitled");
|
||||
String album = (tags.containsKey("ALBUM") ? (String)((Vector)tags.get("ALBUM")).get(0) : "No Album");
|
||||
String artist = (tags.containsKey("ARTIST") ? (String)((Vector)tags.get("ARTIST")).get(0) : "Unknown Artist");
|
||||
|
||||
String songnum = data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER);
|
||||
String composer = data.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER);
|
||||
|
||||
long albumId = MediaLibrary.hash63(album);
|
||||
long artistId = MediaLibrary.hash63(artist);
|
||||
long composerId = MediaLibrary.hash63(composer);
|
||||
|
||||
ContentValues v = new ContentValues();
|
||||
v.put(MediaLibrary.SongColumns._ID, songId);
|
||||
v.put(MediaLibrary.SongColumns.TITLE, title);
|
||||
v.put(MediaLibrary.SongColumns.TITLE_SORT, MediaLibrary.keyFor(title));
|
||||
v.put(MediaLibrary.SongColumns.ALBUM_ID, albumId);
|
||||
v.put(MediaLibrary.SongColumns.DURATION, duration);
|
||||
v.put(MediaLibrary.SongColumns.SONG_NUMBER,songnum);
|
||||
v.put(MediaLibrary.SongColumns.PATH, path);
|
||||
v.put(MediaLibrary.SongColumns._ID, songId);
|
||||
v.put(MediaLibrary.SongColumns.TITLE, title);
|
||||
v.put(MediaLibrary.SongColumns.TITLE_SORT, MediaLibrary.keyFor(title));
|
||||
v.put(MediaLibrary.SongColumns.ALBUM_ID, albumId);
|
||||
v.put(MediaLibrary.SongColumns.DURATION, tags.getFirst(MediaMetadataExtractor.DURATION));
|
||||
v.put(MediaLibrary.SongColumns.SONG_NUMBER, tags.getFirst(MediaMetadataExtractor.TRACK_NUMBER));
|
||||
v.put(MediaLibrary.SongColumns.PATH, path);
|
||||
mBackend.insert(MediaLibrary.TABLE_SONGS, null, v);
|
||||
|
||||
v.clear();
|
||||
v.put(MediaLibrary.AlbumColumns._ID, albumId);
|
||||
v.put(MediaLibrary.AlbumColumns.ALBUM, album);
|
||||
v.put(MediaLibrary.AlbumColumns.ALBUM_SORT, MediaLibrary.keyFor(album));
|
||||
v.put(MediaLibrary.AlbumColumns._ID, albumId);
|
||||
v.put(MediaLibrary.AlbumColumns.ALBUM, album);
|
||||
v.put(MediaLibrary.AlbumColumns.ALBUM_SORT, MediaLibrary.keyFor(album));
|
||||
v.put(MediaLibrary.AlbumColumns.PRIMARY_ARTIST_ID, artistId);
|
||||
v.put(MediaLibrary.AlbumColumns.YEAR, tags.getFirst(MediaMetadataExtractor.YEAR));
|
||||
v.put(MediaLibrary.AlbumColumns.DISC_NUMBER, tags.getFirst(MediaMetadataExtractor.DISC_NUMBER));
|
||||
mBackend.insert(MediaLibrary.TABLE_ALBUMS, null, v);
|
||||
|
||||
v.clear();
|
||||
v.put(MediaLibrary.ContributorColumns._ID, artistId);
|
||||
v.put(MediaLibrary.ContributorColumns._ID, artistId);
|
||||
v.put(MediaLibrary.ContributorColumns._CONTRIBUTOR, artist);
|
||||
v.put(MediaLibrary.ContributorColumns._CONTRIBUTOR_SORT, MediaLibrary.keyFor(artist));
|
||||
mBackend.insert(MediaLibrary.TABLE_CONTRIBUTORS, null, v);
|
||||
|
||||
v.clear();
|
||||
v.put(MediaLibrary.ContributorSongColumns._CONTRIBUTOR_ID, artistId);
|
||||
v.put(MediaLibrary.ContributorSongColumns.SONG_ID, songId);
|
||||
v.put(MediaLibrary.ContributorSongColumns.ROLE, 0);
|
||||
v.put(MediaLibrary.ContributorSongColumns.SONG_ID, songId);
|
||||
v.put(MediaLibrary.ContributorSongColumns.ROLE, 0);
|
||||
mBackend.insert(MediaLibrary.TABLE_CONTRIBUTORS_SONGS, null, v);
|
||||
|
||||
// Composers are optional: only add if we found it
|
||||
String composer = tags.getFirst(MediaMetadataExtractor.COMPOSER);
|
||||
if (composer != null) {
|
||||
long composerId = MediaLibrary.hash63(composer);
|
||||
v.clear();
|
||||
v.put(MediaLibrary.ContributorColumns._ID, composerId);
|
||||
v.put(MediaLibrary.ContributorColumns._ID, composerId);
|
||||
v.put(MediaLibrary.ContributorColumns._CONTRIBUTOR, composer);
|
||||
v.put(MediaLibrary.ContributorColumns._CONTRIBUTOR_SORT, MediaLibrary.keyFor(composer));
|
||||
mBackend.insert(MediaLibrary.TABLE_CONTRIBUTORS, null, v);
|
||||
|
||||
v.clear();
|
||||
v.put(MediaLibrary.ContributorSongColumns._CONTRIBUTOR_ID, composerId);
|
||||
v.put(MediaLibrary.ContributorSongColumns.SONG_ID, songId);
|
||||
v.put(MediaLibrary.ContributorSongColumns.ROLE, 1);
|
||||
v.put(MediaLibrary.ContributorSongColumns.SONG_ID, songId);
|
||||
v.put(MediaLibrary.ContributorSongColumns.ROLE, 1);
|
||||
mBackend.insert(MediaLibrary.TABLE_CONTRIBUTORS_SONGS, null, v);
|
||||
}
|
||||
|
||||
if (tags.containsKey("GENRE")) {
|
||||
Vector<String> genres = (Vector)tags.get("GENRE");
|
||||
// A song might be in multiple genres
|
||||
if (tags.containsKey(MediaMetadataExtractor.GENRE)) {
|
||||
ArrayList<String> genres = tags.get(MediaMetadataExtractor.GENRE);
|
||||
for (String genre : genres) {
|
||||
long genreId = MediaLibrary.hash63(genre);
|
||||
v.clear();
|
||||
|
Loading…
x
Reference in New Issue
Block a user