add metadata extraction wrapper

This commit is contained in:
Adrian Ulrich 2016-11-26 18:57:41 +01:00
parent 80744ae9cc
commit 2df519f0f6
3 changed files with 192 additions and 47 deletions

View File

@ -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);
}
// }
}

View File

@ -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);
}
}
}
}

View File

@ -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();