Commit 82f9be23 authored by DrKLO's avatar DrKLO

Update to 3.1.1

parent a93d2994
......@@ -73,7 +73,7 @@ android {
defaultConfig {
minSdkVersion 8
targetSdkVersion 22
versionCode 572
versionName "3.0.1"
versionCode 580
versionName "3.1.1"
}
}
......@@ -161,8 +161,20 @@
<service android:name="org.telegram.android.NotificationsService" android:enabled="true"/>
<service android:name="org.telegram.android.NotificationRepeat" android:exported="false"/>
<service android:name="org.telegram.android.NotificationDelay" android:exported="false"/>
<service android:name="org.telegram.android.VideoEncodingService" android:enabled="true"/>
<service android:name="org.telegram.android.MusicPlayerService" android:exported="true" android:enabled="true"/>
<receiver android:name="org.telegram.android.MusicPlayerReceiver" >
<intent-filter>
<action android:name="org.telegram.android.musicplayer.close" />
<action android:name="org.telegram.android.musicplayer.pause" />
<action android:name="org.telegram.android.musicplayer.next" />
<action android:name="org.telegram.android.musicplayer.play" />
<action android:name="org.telegram.android.musicplayer.previous" />
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
<receiver android:name="org.telegram.android.AppStartReceiver" android:enabled="true">
<intent-filter>
......
......@@ -8,7 +8,6 @@
package org.telegram.SQLite;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.FileLog;
import java.nio.ByteBuffer;
......@@ -30,7 +29,7 @@ public class SQLitePreparedStatement {
public SQLitePreparedStatement(SQLiteDatabase db, String sql, boolean finalize) throws SQLiteException {
finalizeAfterQuery = finalize;
sqliteStatementHandle = prepare(db.getSQLiteHandle(), sql);
if (BuildVars.DEBUG_VERSION) {
/*if (BuildVars.DEBUG_VERSION) {
if (hashMap == null) {
hashMap = new HashMap<>();
}
......@@ -38,7 +37,7 @@ public class SQLitePreparedStatement {
for (HashMap.Entry<SQLitePreparedStatement, String> entry : hashMap.entrySet()) {
FileLog.d("tmessages", "exist entry = " + entry.getValue());
}
}
}*/
}
......@@ -101,9 +100,9 @@ public class SQLitePreparedStatement {
return;
}
try {
if (BuildVars.DEBUG_VERSION) {
/*if (BuildVars.DEBUG_VERSION) {
hashMap.remove(this);
}
}*/
isFinalized = true;
finalize(sqliteStatementHandle);
} catch (SQLiteException e) {
......
......@@ -542,16 +542,6 @@ public class AndroidUtilities {
return 0;
}
public static int getCurrentActionBarHeight() {
if (isTablet()) {
return dp(64);
} else if (ApplicationLoader.applicationContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
return dp(48);
} else {
return dp(56);
}
}
public static Point getRealScreenSize() {
Point size = new Point();
try {
......
......@@ -66,13 +66,13 @@ public class AnimatorSetProxy {
public void playTogether(ArrayList<Object> items) {
if (View10.NEED_PROXY) {
ArrayList<Animator10> animators = new ArrayList<Animator10>();
ArrayList<Animator10> animators = new ArrayList<>();
for (Object obj : items) {
animators.add((Animator10)obj);
}
((AnimatorSet10) animatorSet).playTogether(animators);
} else {
ArrayList<Animator> animators = new ArrayList<Animator>();
ArrayList<Animator> animators = new ArrayList<>();
for (Object obj : items) {
animators.add((Animator)obj);
}
......
......@@ -1285,6 +1285,9 @@ public class ImageLoader {
}
public Float getFileProgress(String location) {
if (location == null) {
return null;
}
return fileProgresses.get(location);
}
......
......@@ -313,6 +313,8 @@ public class MessageObject {
} else {
messageText = LocaleController.getString("AttachSticker", R.string.AttachSticker);
}
} else if (isMusic()) {
messageText = LocaleController.getString("AttachMusic", R.string.AttachMusic);
} else {
String name = FileLoader.getDocumentFileName(message.media.document);
if (name != null && name.length() > 0) {
......@@ -327,7 +329,9 @@ public class MessageObject {
} else {
messageText = message.message;
}
messageText = Emoji.replaceEmoji(messageText, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20));
if (generateLayout) {
messageText = Emoji.replaceEmoji(messageText, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false);
}
if (message instanceof TLRPC.TL_message || message instanceof TLRPC.TL_messageForwarded_old2) {
if (isMediaEmpty()) {
......@@ -355,6 +359,9 @@ public class MessageObject {
type = 8;
} else if (message.media.document.mime_type.equals("image/webp") && isSticker()) {
type = 13;
} else if (isMusic()) {
type = 14;
contentType = 8;
} else {
type = 9;
}
......@@ -600,7 +607,7 @@ public class MessageObject {
return;
}
if (messageOwner.media != null && messageOwner.media.caption != null && messageOwner.media.caption.length() > 0) {
caption = Emoji.replaceEmoji(messageOwner.media.caption, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20));
caption = Emoji.replaceEmoji(messageOwner.media.caption, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false);
if (containsUrls(caption)) {
try {
Linkify.addLinks((Spannable) caption, Linkify.WEB_URLS);
......@@ -950,6 +957,17 @@ public class MessageObject {
return false;
}
public static boolean isMusicMessage(TLRPC.Message message) {
if (message.media != null && message.media.document != null) {
for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) {
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
return true;
}
}
}
return false;
}
public static TLRPC.InputStickerSet getInputStickerSet(TLRPC.Message message) {
if (message.media != null && message.media.document != null) {
for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) {
......@@ -986,6 +1004,8 @@ public class MessageObject {
return AndroidUtilities.dp(100);
} else if (type == 4) {
return AndroidUtilities.dp(114);
} else if (type == 14) {
return AndroidUtilities.dp(78);
} else if (type == 13) {
float maxHeight = AndroidUtilities.displaySize.y * 0.4f;
float maxWidth;
......@@ -1061,6 +1081,39 @@ public class MessageObject {
return isStickerMessage(messageOwner);
}
public boolean isMusic() {
return isMusicMessage(messageOwner);
}
public String getMusicTitle() {
for (TLRPC.DocumentAttribute attribute : messageOwner.media.document.attributes) {
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
String title = attribute.title;
if (title == null || title.length() == 0) {
title = FileLoader.getDocumentFileName(messageOwner.media.document);
if (title == null || title.length() == 0) {
title = LocaleController.getString("AudioUnknownTitle", R.string.AudioUnknownTitle);
}
}
return title;
}
}
return "";
}
public String getMusicAuthor() {
for (TLRPC.DocumentAttribute attribute : messageOwner.media.document.attributes) {
if (attribute instanceof TLRPC.TL_documentAttributeAudio) {
String performer = attribute.performer;
if (performer == null || performer.length() == 0) {
performer = LocaleController.getString("AudioUnknownArtist", R.string.AudioUnknownArtist);
}
return performer;
}
}
return "";
}
public TLRPC.InputStickerSet getInputStickerSet() {
return getInputStickerSet(messageOwner);
}
......
......@@ -3900,7 +3900,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter
}
if (!markAsReadMessagesInbox.isEmpty() || !markAsReadMessagesOutbox.isEmpty() || !markAsReadEncrypted.isEmpty()) {
if (!markAsReadMessagesInbox.isEmpty() || !markAsReadMessagesOutbox.isEmpty()) {
MessagesStorage.getInstance().updateDialogsWithReadedMessages(markAsReadMessagesInbox, true);
MessagesStorage.getInstance().updateDialogsWithReadMessages(markAsReadMessagesInbox, true);
}
MessagesStorage.getInstance().markMessagesAsRead(markAsReadMessagesInbox, markAsReadMessagesOutbox, markAsReadEncrypted, true);
}
......
......@@ -1149,7 +1149,7 @@ public class MessagesStorage {
});
}
private void updateDialogsWithReadedMessagesInternal(final ArrayList<Integer> messages, final HashMap<Integer, Integer> inbox) {
private void updateDialogsWithReadMessagesInternal(final ArrayList<Integer> messages, final HashMap<Integer, Integer> inbox) {
try {
HashMap<Long, Integer> dialogsToUpdate = new HashMap<>();
StringBuilder dialogsToReload = new StringBuilder();
......@@ -1184,14 +1184,13 @@ public class MessagesStorage {
SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(mid) FROM messages WHERE uid = %d AND mid <= %d AND read_state IN(0,2) AND out = 0", entry.getKey(), entry.getValue()));
if (cursor.next()) {
int count = cursor.intValue(0);
if (count == 0) {
continue;
}
dialogsToUpdate.put((long) entry.getKey(), count);
if (dialogsToReload.length() != 0) {
dialogsToReload.append(",");
if (count != 0) {
dialogsToUpdate.put((long) entry.getKey(), count);
if (dialogsToReload.length() != 0) {
dialogsToReload.append(",");
}
dialogsToReload.append(entry.getKey());
}
dialogsToReload.append(entry.getKey());
}
cursor.dispose();
}
......@@ -1231,7 +1230,7 @@ public class MessagesStorage {
}
}
public void updateDialogsWithReadedMessages(final HashMap<Integer, Integer> inbox, boolean useQueue) {
public void updateDialogsWithReadMessages(final HashMap<Integer, Integer> inbox, boolean useQueue) {
if (inbox.isEmpty()) {
return;
}
......@@ -1239,11 +1238,11 @@ public class MessagesStorage {
storageQueue.postRunnable(new Runnable() {
@Override
public void run() {
updateDialogsWithReadedMessagesInternal(null, inbox);
updateDialogsWithReadMessagesInternal(null, inbox);
}
});
} else {
updateDialogsWithReadedMessagesInternal(null, inbox);
updateDialogsWithReadMessagesInternal(null, inbox);
}
}
......@@ -3473,7 +3472,7 @@ public class MessagesStorage {
NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDeleted, mids);
}
});
MessagesStorage.getInstance().updateDialogsWithReadedMessagesInternal(mids, null);
MessagesStorage.getInstance().updateDialogsWithReadMessagesInternal(mids, null);
MessagesStorage.getInstance().markMessagesAsDeletedInternal(mids);
MessagesStorage.getInstance().updateDialogsWithDeletedMessagesInternal(mids);
}
......
/*
* This is the source code of Telegram for Android v. 2.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2015.
*/
package org.telegram.android;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.view.KeyEvent;
public class MusicPlayerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON)) {
if (intent.getExtras() == null) {
return;
}
KeyEvent keyEvent = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
if (keyEvent == null) {
return;
}
if (keyEvent.getAction() != KeyEvent.ACTION_DOWN)
return;
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
if (MediaController.getInstance().isAudioPaused()) {
MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject());
} else {
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
}
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject());
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
break;
case KeyEvent.KEYCODE_MEDIA_STOP:
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
MediaController.getInstance().playNextMessage();
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
MediaController.getInstance().playPreviousMessage();
break;
}
} else {
if (intent.getAction().equals(MusicPlayerService.NOTIFY_PLAY)) {
MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject());
} else if (intent.getAction().equals(MusicPlayerService.NOTIFY_PAUSE) || intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject());
} else if (intent.getAction().equals(MusicPlayerService.NOTIFY_NEXT)) {
MediaController.getInstance().playNextMessage();
} else if (intent.getAction().equals(MusicPlayerService.NOTIFY_CLOSE)) {
MediaController.getInstance().clenupPlayer(true, true);
} else if (intent.getAction().equals(MusicPlayerService.NOTIFY_PREVIOUS)) {
MediaController.getInstance().playPreviousMessage();
}
}
}
}
......@@ -64,6 +64,7 @@ public class NotificationCenter {
public static final int botInfoDidLoaded = totalEvents++;
public static final int botKeyboardDidLoaded = totalEvents++;
public static final int chatSearchResultsAvailable = totalEvents++;
public static final int musicDidLoaded = totalEvents++;
public static final int httpFileDidLoaded = totalEvents++;
public static final int httpFileDidFailedLoad = totalEvents++;
......@@ -89,6 +90,7 @@ public class NotificationCenter {
public static final int audioProgressDidChanged = totalEvents++;
public static final int audioDidReset = totalEvents++;
public static final int audioPlayStateChanged = totalEvents++;
public static final int recordProgressChanged = totalEvents++;
public static final int recordStarted = totalEvents++;
public static final int recordStartError = totalEvents++;
......
......@@ -31,8 +31,6 @@ import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.app.RemoteInput;
import org.json.JSONArray;
import org.json.JSONObject;
import org.telegram.messenger.ConnectionsManager;
import org.telegram.messenger.DispatchQueue;
import org.telegram.messenger.FileLog;
......@@ -602,11 +600,8 @@ public class NotificationsController {
}
String lastMessage = null;
String lastMessageFull = null;
if (pushMessages.size() == 1) {
String message = lastMessageFull = getStringForMessage(pushMessages.get(0), false);
//lastMessage = getStringForMessage(pushMessages.get(0), true);
lastMessage = lastMessageFull;
String message = lastMessage = getStringForMessage(pushMessages.get(0), false);
if (message == null) {
return;
}
......@@ -630,8 +625,7 @@ public class NotificationsController {
continue;
}
if (i == 0) {
lastMessageFull = message;
lastMessage = lastMessageFull;
lastMessage = message;
}
if (pushDialogs.size() == 1) {
if (replace) {
......@@ -692,9 +686,6 @@ public class NotificationsController {
showExtraNotifications(mBuilder, notifyAboutLast);
notificationManager.notify(1, mBuilder.build());
if (preferences.getBoolean("EnablePebbleNotifications", false)) {
sendAlertToPebble(lastMessageFull);
}
scheduleNotificationRepeat();
} catch (Exception e) {
......@@ -897,26 +888,6 @@ public class NotificationsController {
}
}
private void sendAlertToPebble(String message) {
try {
final Intent i = new Intent("com.getpebble.action.SEND_NOTIFICATION");
final HashMap<String, String> data = new HashMap<>();
data.put("title", LocaleController.getString("AppName", R.string.AppName));
data.put("body", message);
final JSONObject jsonData = new JSONObject(data);
final String notificationData = new JSONArray().put(jsonData).toString();
i.putExtra("messageType", "PEBBLE_ALERT");
i.putExtra("sender", LocaleController.formatString("AppName", R.string.AppName));
i.putExtra("notificationData", notificationData);
ApplicationLoader.applicationContext.sendBroadcast(i);
} catch (Exception e) {
FileLog.e("tmessages", e);
}
}
public void processReadMessages(HashMap<Integer, Integer> inbox, long dialog_id, int max_date, int max_id, boolean isPopup) {
int oldCount = popupMessages.size();
if (inbox != null) {
......
......@@ -19,6 +19,7 @@ import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
import android.widget.Toast;
import org.telegram.android.audioinfo.AudioInfo;
import org.telegram.messenger.ConnectionsManager;
import org.telegram.messenger.FileLoader;
import org.telegram.messenger.FileLog;
......@@ -557,6 +558,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
for (int a = 0; a < messages.size(); a++) {
MessageObject msgObj = messages.get(a);
if (msgObj.getId() <= 0) {
continue;
}
final TLRPC.Message newMsg = new TLRPC.TL_message();
newMsg.flags |= TLRPC.MESSAGE_FLAG_FWD;
......@@ -1858,6 +1862,7 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
return false;
}
MimeTypeMap myMime = MimeTypeMap.getSingleton();
TLRPC.TL_documentAttributeAudio attributeAudio = null;
if (uri != null) {
String extension = null;
if (mime != null) {
......@@ -1885,8 +1890,31 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
if (idx != -1) {
ext = path.substring(idx + 1);
}
if (ext.toLowerCase().equals("mp3") || ext.toLowerCase().equals("m4a")) {
AudioInfo audioInfo = AudioInfo.getAudioInfo(f);
if (audioInfo != null && audioInfo.getDuration() != 0) {
if (isEncrypted) {
attributeAudio = new TLRPC.TL_documentAttributeAudio_old();
} else {
attributeAudio = new TLRPC.TL_documentAttributeAudio();
}
attributeAudio.duration = (int) (audioInfo.getDuration() / 1000);
attributeAudio.title = audioInfo.getTitle();
attributeAudio.performer = audioInfo.getArtist();
if (attributeAudio.title == null) {
attributeAudio.title = "";
}
if (attributeAudio.performer == null) {
attributeAudio.performer = "";
}
}
}
if (originalPath != null) {
originalPath += "" + f.length();
if (attributeAudio != null) {
originalPath += "audio" + f.length();
} else {
originalPath += "" + f.length();
}
}
TLRPC.TL_document document = null;
......@@ -1905,6 +1933,9 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
document.attributes.add(fileName);
document.size = (int) f.length();
document.dc_id = 0;
if (attributeAudio != null) {
document.attributes.add(attributeAudio);
}
if (ext.length() != 0) {
if (ext.toLowerCase().equals("webp")) {
document.mime_type = "image/webp";
......@@ -1990,6 +2021,56 @@ public class SendMessagesHelper implements NotificationCenter.NotificationCenter
prepareSendingDocuments(paths, originalPaths, uris, mine, dialog_id, reply_to_msg);
}
public static void prepareSendingAudioDocuments(final ArrayList<MessageObject> messageObjects, final long dialog_id, final MessageObject reply_to_msg) {
new Thread(new Runnable() {
@Override
public void run() {
int size = messageObjects.size();
for (int a = 0; a < size; a++) {
final MessageObject messageObject = messageObjects.get(a);
String originalPath = messageObject.messageOwner.attachPath;
final File f = new File(originalPath);
boolean isEncrypted = (int) dialog_id == 0;
if (originalPath != null) {
originalPath += "audio" + f.length();
}
TLRPC.TL_document document = null;
if (!isEncrypted) {
document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 1 : 4);
}
if (document == null) {
document = (TLRPC.TL_document) messageObject.messageOwner.media.document;
}
if (isEncrypted) {
for (int b = 0; b < document.attributes.size(); b++) {
if (document.attributes.get(b) instanceof TLRPC.TL_documentAttributeAudio) {
TLRPC.TL_documentAttributeAudio_old old = new TLRPC.TL_documentAttributeAudio_old();
old.duration = document.attributes.get(b).duration;
document.attributes.remove(b);
document.attributes.add(old);
break;
}
}
}
final String originalPathFinal = originalPath;
final TLRPC.TL_document documentFinal = document;
AndroidUtilities.runOnUIThread(new Runnable() {
@Override
public void run() {
SendMessagesHelper.getInstance().sendMessage(documentFinal, originalPathFinal, messageObject.messageOwner.attachPath, dialog_id, reply_to_msg);
}
});
}
}
}).start();
}
public static void prepareSendingDocuments(final ArrayList<String> paths, final ArrayList<String> originalPaths, final ArrayList<Uri> uris, final String mime, final long dialog_id, final MessageObject reply_to_msg) {
if (paths == null && originalPaths == null && uris == null || paths != null && originalPaths != null && paths.size() != originalPaths.size()) {
return;
......
......@@ -8,6 +8,7 @@
package org.telegram.android;
import org.telegram.PhoneFormat.PhoneFormat;
import org.telegram.messenger.R;
import org.telegram.messenger.TLRPC;
......@@ -29,7 +30,8 @@ public class UserObject {
if (user == null || isDeleted(user)) {
return LocaleController.getString("HiddenName", R.string.HiddenName);
}
return ContactsController.formatName(user.first_name, user.last_name);
String name = ContactsController.formatName(user.first_name, user.last_name);
return name.length() != 0 || user.phone == null || user.phone.length() == 0 ? name : PhoneFormat.getInstance().format("+" + user.phone);
}
public static String getFirstName(TLRPC.User user) {
......@@ -40,6 +42,6 @@ public class UserObject {
if (name == null || name.length() == 0) {
name = user.last_name;
}
return name != null && name.length() > 0 ? name : "DELETED";
return name != null && name.length() > 0 ? name : LocaleController.getString("HiddenName", R.string.HiddenName);
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo;
import android.graphics.Bitmap;
import org.telegram.android.audioinfo.m4a.M4AInfo;
import org.telegram.android.audioinfo.mp3.MP3Info;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
public abstract class AudioInfo {
protected String brand; // brand, e.g. "M4A", "ID3", ...
protected String version; // version, e.g. "0", "2.3.0", ...
protected long duration; // track duration (milliseconds)
protected String title; // track title
protected String artist; // track artist
protected String albumArtist; // album artist
protected String album; // album title
protected short year; // year...
protected String genre; // genre name
protected String comment; // comment...
protected short track; // track number
protected short tracks; // number of tracks
protected short disc; // disc number
protected short discs; // number of discs
protected String copyright; // copyright notice
protected String composer; // composer name
protected String grouping; // track grouping
protected boolean compilation; // compilation flag
protected String lyrics; // song lyrics
protected Bitmap cover; // cover image data
protected Bitmap smallCover; // cover image data
public String getBrand() {
return brand;
}
public String getVersion() {
return version;
}
public long getDuration() {
return duration;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public String getAlbumArtist() {
return albumArtist;
}
public String getAlbum() {
return album;
}
public short getYear() {
return year;
}
public String getGenre() {
return genre;
}
public String getComment() {
return comment;
}
public short getTrack() {
return track;
}
public short getTracks() {
return tracks;
}
public short getDisc() {
return disc;
}
public short getDiscs() {
return discs;
}
public String getCopyright() {
return copyright;
}
public String getComposer() {
return composer;
}
public String getGrouping() {
return grouping;
}
public boolean isCompilation() {
return compilation;
}
public String getLyrics() {
return lyrics;
}
public Bitmap getCover() {
return cover;
}
public Bitmap getSmallCover() {
return smallCover;
}
public static AudioInfo getAudioInfo(File file) {
try {
byte header[] = new byte[12];
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
randomAccessFile.readFully(header, 0, 8);
randomAccessFile.close();
InputStream input = new BufferedInputStream(new FileInputStream(file));
if (header[4] == 'f' && header[5] == 't' && header[6] == 'y' && header[7] == 'p') {
return new M4AInfo(input);
} else {
return new MP3Info(input, file.length());
}
} catch (Exception e) {
return null;
}
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.m4a;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import org.telegram.android.audioinfo.AudioInfo;
import org.telegram.android.audioinfo.mp3.ID3v1Genre;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.logging.Level;
import java.util.logging.Logger;
public class M4AInfo extends AudioInfo {
static final Logger LOGGER = Logger.getLogger(M4AInfo.class.getName());
private static final String ASCII = "ISO8859_1";
private static final String UTF_8 = "UTF-8";
private BigDecimal volume; // normal = 1.0
private BigDecimal speed; // normal = 1.0
private short tempo;
private byte rating; // none = 0, clean = 2, explicit = 4
private final Level debugLevel;
public M4AInfo(InputStream input) throws IOException {
this(input, Level.FINEST);
}
public M4AInfo(InputStream input, Level debugLevel) throws IOException {
this.debugLevel = debugLevel;
MP4Input mp4 = new MP4Input(input);
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, mp4.toString());
}
ftyp(mp4.nextChild("ftyp"));
moov(mp4.nextChildUpTo("moov"));
}
void ftyp(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
brand = atom.readString(4, ASCII).trim();
if (brand.matches("M4V|MP4|mp42|isom")) { // experimental file types
LOGGER.warning(atom.getPath() + ": brand=" + brand + " (experimental)");
} else if (!brand.matches("M4A|M4P")) {
LOGGER.warning(atom.getPath() + ": brand=" + brand + " (expected M4A or M4P)");
}
version = String.valueOf(atom.readInt());
}
void moov(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
while (atom.hasMoreChildren()) {
MP4Atom child = atom.nextChild();
switch (child.getType()) {
case "mvhd":
mvhd(child);
break;
case "trak":
trak(child);
break;
case "udta":
udta(child);
break;
default:
break;
}
}
}
void mvhd(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
byte version = atom.readByte();
atom.skip(3); // flags
atom.skip(version == 1 ? 16 : 8); // created/modified date
int scale = atom.readInt();
long units = version == 1 ? atom.readLong() : atom.readInt();
if (duration == 0) {
duration = 1000 * units / scale;
} else if (LOGGER.isLoggable(debugLevel) && Math.abs(duration - 1000 * units / scale) > 2) {
LOGGER.log(debugLevel, "mvhd: duration " + duration + " -> " + (1000 * units / scale));
}
speed = atom.readIntegerFixedPoint();
volume = atom.readShortFixedPoint();
}
void trak(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
mdia(atom.nextChildUpTo("mdia"));
}
void mdia(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
mdhd(atom.nextChild("mdhd"));
}
void mdhd(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
byte version = atom.readByte();
atom.skip(3);
atom.skip(version == 1 ? 16 : 8); // created/modified date
int sampleRate = atom.readInt();
long samples = version == 1 ? atom.readLong() : atom.readInt();
if (duration == 0) {
duration = 1000 * samples / sampleRate;
} else if (LOGGER.isLoggable(debugLevel) && Math.abs(duration - 1000 * samples / sampleRate) > 2) {
LOGGER.log(debugLevel, "mdhd: duration " + duration + " -> " + (1000 * samples / sampleRate));
}
}
void udta(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
while (atom.hasMoreChildren()) {
MP4Atom child = atom.nextChild();
if ("meta".equals(child.getType())) {
meta(child);
break;
}
}
}
void meta(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
atom.skip(4); // version/flags
while (atom.hasMoreChildren()) {
MP4Atom child = atom.nextChild();
if ("ilst".equals(child.getType())) {
ilst(child);
break;
}
}
}
void ilst(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
while (atom.hasMoreChildren()) {
MP4Atom child = atom.nextChild();
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, child.toString());
}
if (child.getRemaining() == 0) {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, child.getPath() + ": contains no value");
}
continue;
}
data(child.nextChildUpTo("data"));
}
}
void data(MP4Atom atom) throws IOException {
if (LOGGER.isLoggable(debugLevel)) {
LOGGER.log(debugLevel, atom.toString());
}
atom.skip(4); // version & flags
atom.skip(4); // reserved
switch (atom.getParent().getType()) {
case "©alb":
album = atom.readString(UTF_8);
break;
case "aART":
albumArtist = atom.readString(UTF_8);
break;
case "©ART":
artist = atom.readString(UTF_8);
break;
case "©cmt":
comment = atom.readString(UTF_8);
break;
case "©com":
case "©wrt":
if (composer == null || composer.trim().length() == 0) {
composer = atom.readString(UTF_8);
}
break;
case "covr":
try {
byte[] bytes = atom.readBytes();
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
opts.inSampleSize = 1;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
if (opts.outWidth > 800 || opts.outHeight > 800) {
int size = Math.max(opts.outWidth, opts.outHeight);
while (size > 800) {
opts.inSampleSize *= 2;
size /= 2;
}
}
opts.inJustDecodeBounds = false;
cover = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
if (cover != null) {
float scale = Math.max(cover.getWidth(), cover.getHeight()) / 120.0f;
if (scale > 0) {
smallCover = Bitmap.createScaledBitmap(cover, (int) (cover.getWidth() / scale), (int) (cover.getHeight() / scale), true);
} else {
smallCover = cover;
}
if (smallCover == null) {
smallCover = cover;
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
case "cpil":
compilation = atom.readBoolean();
break;
case "cprt":
case "©cpy":
if (copyright == null || copyright.trim().length() == 0) {
copyright = atom.readString(UTF_8);
}
break;
case "©day":
String day = atom.readString(UTF_8).trim();
if (day.length() >= 4) {
try {
year = Short.valueOf(day.substring(0, 4));
} catch (NumberFormatException e) {
// ignore
}
}
break;
case "disk":
atom.skip(2); // padding?
disc = atom.readShort();
discs = atom.readShort();
break;
case "gnre":
if (genre == null || genre.trim().length() == 0) {
if (atom.getRemaining() == 2) { // id3v1 genre?
int index = atom.readShort() - 1;
ID3v1Genre id3v1Genre = ID3v1Genre.getGenre(index);
if (id3v1Genre != null) {
genre = id3v1Genre.getDescription();
}
} else {
genre = atom.readString(UTF_8);
}
}
break;
case "©gen":
if (genre == null || genre.trim().length() == 0) {
genre = atom.readString(UTF_8);
}
break;
case "©grp":
grouping = atom.readString(UTF_8);
break;
case "©lyr":
lyrics = atom.readString(UTF_8);
break;
case "©nam":
title = atom.readString(UTF_8);
break;
case "rtng":
rating = atom.readByte();
break;
case "tmpo":
tempo = atom.readShort();
break;
case "trkn":
atom.skip(2); // padding?
track = atom.readShort();
tracks = atom.readShort();
break;
default:
break;
}
}
public short getTempo() {
return tempo;
}
public byte getRating() {
return rating;
}
public BigDecimal getSpeed() {
return speed;
}
public BigDecimal getVolume() {
return volume;
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.m4a;
import org.telegram.android.audioinfo.util.RangeInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.math.BigDecimal;
public class MP4Atom extends MP4Box<RangeInputStream> {
public MP4Atom(RangeInputStream input, MP4Box<?> parent, String type) {
super(input, parent, type);
}
public long getLength() {
return getInput().getPosition() + getInput().getRemainingLength();
}
public long getOffset() {
return getParent().getPosition() - getPosition();
}
public long getRemaining() {
return getInput().getRemainingLength();
}
public boolean hasMoreChildren() {
return (getChild() != null ? getChild().getRemaining() : 0) < getRemaining();
}
public MP4Atom nextChildUpTo(String expectedTypeExpression) throws IOException {
while (getRemaining() > 0) {
MP4Atom atom = nextChild();
if (atom.getType().matches(expectedTypeExpression)) {
return atom;
}
}
throw new IOException("atom type mismatch, not found: " + expectedTypeExpression);
}
public boolean readBoolean() throws IOException {
return data.readBoolean();
}
public byte readByte() throws IOException {
return data.readByte();
}
public short readShort() throws IOException {
return data.readShort();
}
public int readInt() throws IOException {
return data.readInt();
}
public long readLong() throws IOException {
return data.readLong();
}
public byte[] readBytes(int len) throws IOException {
byte[] bytes = new byte[len];
data.readFully(bytes);
return bytes;
}
public byte[] readBytes() throws IOException {
return readBytes((int) getRemaining());
}
public BigDecimal readShortFixedPoint() throws IOException {
int integer = data.readByte();
int decimal = data.readUnsignedByte();
return new BigDecimal(String.valueOf(integer) + "" + String.valueOf(decimal));
}
public BigDecimal readIntegerFixedPoint() throws IOException {
int integer = data.readShort();
int decimal = data.readUnsignedShort();
return new BigDecimal(String.valueOf(integer) + "" + String.valueOf(decimal));
}
public String readString(int len, String enc) throws IOException {
String s = new String(readBytes(len), enc);
int end = s.indexOf(0);
return end < 0 ? s : s.substring(0, end);
}
public String readString(String enc) throws IOException {
return readString((int) getRemaining(), enc);
}
public void skip(int len) throws IOException {
int total = 0;
while (total < len) {
int current = data.skipBytes(len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
}
public void skip() throws IOException {
while (getRemaining() > 0) {
if (getInput().skip(getRemaining()) == 0) {
throw new EOFException("Cannot skip atom");
}
}
}
private StringBuffer appendPath(StringBuffer s, MP4Box<?> box) {
if (box.getParent() != null) {
appendPath(s, box.getParent());
s.append("/");
}
return s.append(box.getType());
}
public String getPath() {
return appendPath(new StringBuffer(), this).toString();
}
public String toString() {
StringBuffer s = new StringBuffer();
appendPath(s, this);
s.append("[off=");
s.append(getOffset());
s.append(",pos=");
s.append(getPosition());
s.append(",len=");
s.append(getLength());
s.append("]");
return s.toString();
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.m4a;
import org.telegram.android.audioinfo.util.PositionInputStream;
import org.telegram.android.audioinfo.util.RangeInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
public class MP4Box<I extends PositionInputStream> {
protected static final String ASCII = "ISO8859_1";
private final I input;
private final MP4Box<?> parent;
private final String type;
protected final DataInput data;
private MP4Atom child;
public MP4Box(I input, MP4Box<?> parent, String type) {
this.input = input;
this.parent = parent;
this.type = type;
this.data = new DataInputStream(input);
}
public String getType() {
return type;
}
public MP4Box<?> getParent() {
return parent;
}
public long getPosition() {
return input.getPosition();
}
public I getInput() {
return input;
}
protected MP4Atom getChild() {
return child;
}
public MP4Atom nextChild() throws IOException {
if (child != null) {
child.skip();
}
int atomLength = data.readInt();
byte[] typeBytes = new byte[4];
data.readFully(typeBytes);
String atomType = new String(typeBytes, ASCII);
RangeInputStream atomInput;
if (atomLength == 1) { // extended length
atomInput = new RangeInputStream(input, 16, data.readLong() - 16);
} else {
atomInput = new RangeInputStream(input, 8, atomLength - 8);
}
return child = new MP4Atom(atomInput, this, atomType);
}
public MP4Atom nextChild(String expectedTypeExpression) throws IOException {
MP4Atom atom = nextChild();
if (atom.getType().matches(expectedTypeExpression)) {
return atom;
}
throw new IOException("atom type mismatch, expected " + expectedTypeExpression + ", got " + atom.getType());
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.m4a;
import org.telegram.android.audioinfo.util.PositionInputStream;
import java.io.IOException;
import java.io.InputStream;
public final class MP4Input extends MP4Box<PositionInputStream> {
public MP4Input(InputStream delegate) {
super(new PositionInputStream(delegate), null, "");
}
public MP4Atom nextChildUpTo(String expectedTypeExpression) throws IOException {
while (true) {
MP4Atom atom = nextChild();
if (atom.getType().matches(expectedTypeExpression)) {
return atom;
}
}
}
public String toString() {
return "mp4[pos=" + getPosition() + "]";
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
public enum ID3v1Genre {
/*
* The following genres is defined in ID3v1 (0-79)
*/
Blues("Blues"),
ClassicRock("Classic Rock"),
Country("Country"),
Dance("Dance"),
Disco("Disco"),
Funk("Funk"),
Grunge("Grunge"),
HipHop("Hip-Hop"),
Jazz("Jazz"),
Metal("Metal"),
NewAge("New Age"),
Oldies("Oldies"),
Other("Other"),
Pop("Pop"),
RnB("R&B"),
Rap("Rap"),
Reggae("Reggae"),
Rock("Rock"),
Techno("Techno"),
Industrial("Industrial"),
Alternative("Alternative"),
Ska("Ska"),
DeathMetal("Death Metal"),
Pranks("Pranks"),
Soundtrack("Soundtrack"),
EuroTechno("Euro-Techno"),
Ambient("Ambient"),
TripHop("Trip-Hop"),
Vocal("Vocal"),
JazzFunk("Jazz+Funk"),
Fusion("Fusion"),
Trance("Trance"),
Classical("Classical"),
Instrumental("Instrumental"),
Acid("Acid"),
House("House"),
Game("Game"),
SoundClip("Sound Clip"),
Gospel("Gospel"),
Noise("Noise"),
AlternRock("AlternRock"),
Bass("Bass"),
Soul("Soul"),
Punk("Punk"),
Space("Space"),
Meditative("Meditative"),
InstrumentalPop("Instrumental Pop"),
InstrumentalRock("Instrumental Rock"),
Ethnic("Ethnic"),
Gothic("Gothic"),
Darkwave("Darkwave"),
TechnoIndustrial("Techno-Industrial"),
Electronic("Electronic"),
PopFolk("Pop-Folk"),
Eurodance("Eurodance"),
Dream("Dream"),
SouthernRock("Southern Rock"),
Comedy("Comedy"),
Cult("Cult"),
Gangsta("Gangsta"),
Top40("Top 40"),
ChristianRap("Christian Rap"),
PopFunk("Pop/Funk"),
Jungle("Jungle"),
NativeAmerican("Native American"),
Cabaret("Cabaret"),
NewWave("New Wave"),
Psychadelic("Psychadelic"),
Rave("Rave"),
Showtunes("Showtunes"),
Trailer("Trailer"),
LoFi("Lo-Fi"),
Tribal("Tribal"),
AcidPunk("Acid Punk"),
AcidJazz("Acid Jazz"),
Polka("Polka"),
Retro("Retro"),
Musical("Musical"),
RockAndRoll("Rock & Roll"),
HardRock("Hard Rock"),
/*
* The following genres are Winamp extensions (80-125)
*/
Folk("Folk"),
FolkRock("Folk-Rock"),
NationalFolk("National Folk"),
Swing("Swing"),
FastFusion("Fast Fusion"),
Bebop("Bebop"),
Latin("Latin"),
Revival("Revival"),
Celtic("Celtic"),
Bluegrass("Bluegrass"),
Avantgarde("Avantgarde"),
GothicRock("Gothic Rock"),
ProgressiveRock("Progressive Rock"),
PsychedelicRock("Psychedelic Rock"),
SymphonicRock("Symphonic Rock"),
SlowRock("Slow Rock"),
BigBand("Big Band"),
Chorus("Chorus"),
EasyListening("Easy Listening"),
Acoustic("Acoustic"),
Humour("Humour"),
Speech("Speech"),
Chanson("Chanson"),
Opera("Opera"),
ChamberMusic("Chamber Music"),
Sonata("Sonata"),
Symphony("Symphony"),
BootyBass("Booty Bass"),
Primus("Primus"),
PornGroove("Porn Groove"),
Satire("Satire"),
SlowJam("Slow Jam"),
Club("Club"),
Tango("Tango"),
Samba("Samba"),
Folklore("Folklore"),
Ballad("Ballad"),
PowerBallad("Power Ballad"),
RhytmicSoul("Rhythmic Soul"),
Freestyle("Freestyle"),
Duet("Duet"),
PunkRock("Punk Rock"),
DrumSolo("Drum Solo"),
ACapella("A capella"),
EuroHouse("Euro-House"),
DanceHall("Dance Hall");
public static ID3v1Genre getGenre(int id) {
ID3v1Genre[] values = values();
return id >= 0 && id < values.length ? values[id] : null;
}
private final String description;
ID3v1Genre(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public int getId() {
return ordinal();
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import org.telegram.android.audioinfo.AudioInfo;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
public class ID3v1Info extends AudioInfo {
public static boolean isID3v1StartPosition(InputStream input) throws IOException {
input.mark(3);
try {
return input.read() == 'T' && input.read() == 'A' && input.read() == 'G';
} finally {
input.reset();
}
}
public ID3v1Info(InputStream input) throws IOException {
if (isID3v1StartPosition(input)) {
brand = "ID3";
version = "1.0";
byte[] bytes = readBytes(input, 128);
title = extractString(bytes, 3, 30);
artist = extractString(bytes, 33, 30);
album = extractString(bytes, 63, 30);
try {
year = Short.parseShort(extractString(bytes, 93, 4));
} catch (NumberFormatException e) {
year = 0;
}
comment = extractString(bytes, 97, 30);
ID3v1Genre id3v1Genre = ID3v1Genre.getGenre(bytes[127]);
if (id3v1Genre != null) {
genre = id3v1Genre.getDescription();
}
/*
* ID3v1.1
*/
if (bytes[125] == 0 && bytes[126] != 0) {
version = "1.1";
track = (short) (bytes[126] & 0xFF);
}
}
}
byte[] readBytes(InputStream input, int len) throws IOException {
int total = 0;
byte[] bytes = new byte[len];
while (total < len) {
int current = input.read(bytes, total, len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
return bytes;
}
String extractString(byte[] bytes, int offset, int length) {
try {
String text = new String(bytes, offset, length, "ISO-8859-1");
int zeroIndex = text.indexOf(0);
return zeroIndex < 0 ? text : text.substring(0, zeroIndex);
} catch (Exception e) {
return "";
}
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
public class ID3v2DataInput {
private final InputStream input;
public ID3v2DataInput(InputStream in) {
this.input = in;
}
public final void readFully(byte b[], int off, int len) throws IOException {
int total = 0;
while (total < len) {
int current = input.read(b, off + total, len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
}
public byte[] readFully(int len) throws IOException {
byte[] bytes = new byte[len];
readFully(bytes, 0, len);
return bytes;
}
public void skipFully(long len) throws IOException {
long total = 0;
while (total < len) {
long current = input.skip(len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
}
public byte readByte() throws IOException {
int b = input.read();
if (b < 0) {
throw new EOFException();
}
return (byte) b;
}
public int readInt() throws IOException {
return ((readByte() & 0xFF) << 24) | ((readByte() & 0xFF) << 16) | ((readByte() & 0xFF) << 8) | (readByte() & 0xFF);
}
public int readSyncsafeInt() throws IOException {
return ((readByte() & 0x7F) << 21) | ((readByte() & 0x7F) << 14) | ((readByte() & 0x7F) << 7) | (readByte() & 0x7F);
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import java.nio.charset.Charset;
public enum ID3v2Encoding {
ISO_8859_1(Charset.forName("ISO-8859-1"), 1),
UTF_16(Charset.forName("UTF-16"), 2),
UTF_16BE(Charset.forName("UTF-16BE"), 2),
UTF_8(Charset.forName("UTF-8"), 1);
private final Charset charset;
private final int zeroBytes;
ID3v2Encoding(Charset charset, int zeroBytes) {
this.charset = charset;
this.zeroBytes = zeroBytes;
}
public Charset getCharset() {
return charset;
}
public int getZeroBytes() {
return zeroBytes;
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
public class ID3v2Exception extends Exception {
private static final long serialVersionUID = 1L;
public ID3v2Exception(String message) {
super(message);
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import org.telegram.android.audioinfo.util.RangeInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ID3v2FrameBody {
static final class Buffer {
byte[] bytes;
Buffer(int initialLength) {
bytes = new byte[initialLength];
}
byte[] bytes(int minLength) {
if (minLength > bytes.length) {
int length = bytes.length * 2;
while (minLength > length) {
length *= 2;
}
bytes = new byte[length];
}
return bytes;
}
}
static final ThreadLocal<Buffer> textBuffer = new ThreadLocal<Buffer>() {
@Override
protected Buffer initialValue() {
return new Buffer(4096);
}
};
private final RangeInputStream input;
private final ID3v2TagHeader tagHeader;
private final ID3v2FrameHeader frameHeader;
private final ID3v2DataInput data;
ID3v2FrameBody(InputStream delegate, long position, int dataLength, ID3v2TagHeader tagHeader, ID3v2FrameHeader frameHeader) throws IOException {
this.input = new RangeInputStream(delegate, position, dataLength);
this.data = new ID3v2DataInput(input);
this.tagHeader = tagHeader;
this.frameHeader = frameHeader;
}
public ID3v2DataInput getData() {
return data;
}
public long getPosition() {
return input.getPosition();
}
public long getRemainingLength() {
return input.getRemainingLength();
}
public ID3v2TagHeader getTagHeader() {
return tagHeader;
}
public ID3v2FrameHeader getFrameHeader() {
return frameHeader;
}
private String extractString(byte[] bytes, int offset, int length, ID3v2Encoding encoding, boolean searchZeros) {
if (searchZeros) {
int zeros = 0;
for (int i = 0; i < length; i++) {
// UTF-16LE may have a zero byte as second byte of a 2-byte character -> skip first zero at odd index
if (bytes[offset + i] == 0 && (encoding != ID3v2Encoding.UTF_16 || zeros != 0 || (offset + i) % 2 == 0)) {
if (++zeros == encoding.getZeroBytes()) {
length = i + 1 - encoding.getZeroBytes();
break;
}
} else {
zeros = 0;
}
}
}
try {
String string = new String(bytes, offset, length, encoding.getCharset().name());
if (string.length() > 0 && string.charAt(0) == '\uFEFF') { // remove BOM
string = string.substring(1);
}
return string;
} catch (Exception e) {
return "";
}
}
public String readZeroTerminatedString(int maxLength, ID3v2Encoding encoding) throws IOException, ID3v2Exception {
int zeros = 0;
int length = Math.min(maxLength, (int) getRemainingLength());
byte[] bytes = textBuffer.get().bytes(length);
for (int i = 0; i < length; i++) {
// UTF-16LE may have a zero byte as second byte of a 2-byte character -> skip first zero at odd index
if ((bytes[i] = data.readByte()) == 0 && (encoding != ID3v2Encoding.UTF_16 || zeros != 0 || i % 2 == 0)) {
if (++zeros == encoding.getZeroBytes()) {
return extractString(bytes, 0, i + 1 - encoding.getZeroBytes(), encoding, false);
}
} else {
zeros = 0;
}
}
throw new ID3v2Exception("Could not read zero-termiated string");
}
public String readFixedLengthString(int length, ID3v2Encoding encoding) throws IOException, ID3v2Exception {
if (length > getRemainingLength()) {
throw new ID3v2Exception("Could not read fixed-length string of length: " + length);
}
byte[] bytes = textBuffer.get().bytes(length);
data.readFully(bytes, 0, length);
return extractString(bytes, 0, length, encoding, true);
}
public ID3v2Encoding readEncoding() throws IOException, ID3v2Exception {
byte value = data.readByte();
switch (value) {
case 0:
return ID3v2Encoding.ISO_8859_1;
case 1:
return ID3v2Encoding.UTF_16;
case 2:
return ID3v2Encoding.UTF_16BE;
case 3:
return ID3v2Encoding.UTF_8;
default:
break;
}
throw new ID3v2Exception("Invalid encoding: " + value);
}
public String toString() {
return "id3v2frame[pos=" + getPosition() + ", " + getRemainingLength() + " left]";
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import java.io.IOException;
public class ID3v2FrameHeader {
private String frameId;
private int headerSize;
private int bodySize;
private boolean unsynchronization;
private boolean compression;
private boolean encryption;
private int dataLengthIndicator;
public ID3v2FrameHeader(ID3v2TagBody input) throws IOException, ID3v2Exception {
long startPosition = input.getPosition();
ID3v2DataInput data = input.getData();
/*
* Frame Id
*/
if (input.getTagHeader().getVersion() == 2) { // $xx xx xx (three characters)
frameId = new String(data.readFully(3), "ISO-8859-1");
} else { // $xx xx xx xx (four characters)
frameId = new String(data.readFully(4), "ISO-8859-1");
}
/*
* Size
*/
if (input.getTagHeader().getVersion() == 2) { // $xx xx xx
bodySize = ((data.readByte() & 0xFF) << 16) | ((data.readByte() & 0xFF) << 8) | (data.readByte() & 0xFF);
} else if (input.getTagHeader().getVersion() == 3) { // $xx xx xx xx
bodySize = data.readInt();
} else { // 4 * %0xxxxxxx (sync-save integer)
bodySize = data.readSyncsafeInt();
}
/*
* Flags
*/
if (input.getTagHeader().getVersion() > 2) { // $xx xx
data.readByte(); // status flags
byte formatFlags = data.readByte();
int compressionMask;
int encryptionMask;
int groupingIdentityMask;
int unsynchronizationMask = 0x00;
int dataLengthIndicatorMask = 0x00;
if (input.getTagHeader().getVersion() == 3) { // %(compression)(encryption)(groupingIdentity)00000
compressionMask = 0x80;
encryptionMask = 0x40;
groupingIdentityMask = 0x20;
} else { // %0(groupingIdentity)00(compression)(encryption)(unsynchronization)(dataLengthIndicator)
groupingIdentityMask = 0x40;
compressionMask = 0x08;
encryptionMask = 0x04;
unsynchronizationMask = 0x02;
dataLengthIndicatorMask = 0x01;
}
compression = (formatFlags & compressionMask) != 0;
unsynchronization = (formatFlags & unsynchronizationMask) != 0;
encryption = (formatFlags & encryptionMask) != 0;
/*
* Read flag attachments in the order of the flags (version dependent).
*/
if (input.getTagHeader().getVersion() == 3) {
if (compression) {
dataLengthIndicator = data.readInt();
bodySize -= 4;
}
if (encryption) {
data.readByte(); // just skip
bodySize -= 1;
}
if ((formatFlags & groupingIdentityMask) != 0) {
data.readByte(); // just skip
bodySize -= 1;
}
} else {
if ((formatFlags & groupingIdentityMask) != 0) {
data.readByte(); // just skip
bodySize -= 1;
}
if (encryption) {
data.readByte(); // just skip
bodySize -= 1;
}
if ((formatFlags & dataLengthIndicatorMask) != 0) {
dataLengthIndicator = data.readSyncsafeInt();
bodySize -= 4;
}
}
}
headerSize = (int) (input.getPosition() - startPosition);
}
public String getFrameId() {
return frameId;
}
public int getHeaderSize() {
return headerSize;
}
public int getBodySize() {
return bodySize;
}
public boolean isCompression() {
return compression;
}
public boolean isEncryption() {
return encryption;
}
public boolean isUnsynchronization() {
return unsynchronization;
}
public int getDataLengthIndicator() {
return dataLengthIndicator;
}
public boolean isValid() {
for (int i = 0; i < frameId.length(); i++) {
if ((frameId.charAt(i) < 'A' || frameId.charAt(i) > 'Z') && (frameId.charAt(i) < '0' || frameId.charAt(i) > '9')) {
return false;
}
}
return bodySize > 0;
}
public boolean isPadding() {
for (int i = 0; i < frameId.length(); i++) {
if (frameId.charAt(0) != 0) {
return false;
}
}
return bodySize == 0;
}
@Override
public String toString() {
return String.format("%s[id=%s, bodysize=%d]", getClass().getSimpleName(), frameId, bodySize);
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import org.telegram.android.audioinfo.util.RangeInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.InflaterInputStream;
public class ID3v2TagBody {
private final RangeInputStream input;
private final ID3v2TagHeader tagHeader;
private final ID3v2DataInput data;
ID3v2TagBody(InputStream delegate, long position, int length, ID3v2TagHeader tagHeader) throws IOException {
this.input = new RangeInputStream(delegate, position, length);
this.data = new ID3v2DataInput(input);
this.tagHeader = tagHeader;
}
public ID3v2DataInput getData() {
return data;
}
public long getPosition() {
return input.getPosition();
}
public long getRemainingLength() {
return input.getRemainingLength();
}
public ID3v2TagHeader getTagHeader() {
return tagHeader;
}
public ID3v2FrameBody frameBody(ID3v2FrameHeader frameHeader) throws IOException, ID3v2Exception {
int dataLength = frameHeader.getBodySize();
InputStream input = this.input;
if (frameHeader.isUnsynchronization()) {
byte[] bytes = data.readFully(frameHeader.getBodySize());
boolean ff = false;
int len = 0;
for (byte b : bytes) {
if (!ff || b != 0) {
bytes[len++] = b;
}
ff = (b == 0xFF);
}
dataLength = len;
input = new ByteArrayInputStream(bytes, 0, len);
}
if (frameHeader.isEncryption()) {
throw new ID3v2Exception("Frame encryption is not supported");
}
if (frameHeader.isCompression()) {
dataLength = frameHeader.getDataLengthIndicator();
input = new InflaterInputStream(input);
}
return new ID3v2FrameBody(input, frameHeader.getHeaderSize(), dataLength, tagHeader, frameHeader);
}
public String toString() {
return "id3v2tag[pos=" + getPosition() + ", " + getRemainingLength() + " left]";
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import org.telegram.android.audioinfo.util.PositionInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ID3v2TagHeader {
private int version = 0;
private int revision = 0;
private int headerSize = 0; // size of header, including extended header (with attachments)
private int totalTagSize = 0; // everything, i.e. inluding tag header, extended header, footer & padding
private int paddingSize = 0; // size of zero padding after frames
private int footerSize = 0; // size of footer (version 4 only)
private boolean unsynchronization;
private boolean compression;
public ID3v2TagHeader(InputStream input) throws IOException, ID3v2Exception {
this(new PositionInputStream(input));
}
ID3v2TagHeader(PositionInputStream input) throws IOException, ID3v2Exception {
long startPosition = input.getPosition();
ID3v2DataInput data = new ID3v2DataInput(input);
/*
* Identifier: "ID3"
*/
String id = new String(data.readFully(3), "ISO-8859-1");
if (!"ID3".equals(id)) {
throw new ID3v2Exception("Invalid ID3 identifier: " + id);
}
/*
* Version: $02, $03 or $04
*/
version = data.readByte();
if (version != 2 && version != 3 && version != 4) {
throw new ID3v2Exception("Unsupported ID3v2 version: " + version);
}
/*
* Revision: $xx
*/
revision = data.readByte();
/*
* Flags (evaluated below)
*/
byte flags = data.readByte();
/*
* Size: 4 * %0xxxxxxx (sync-save integer)
*/
totalTagSize = 10 + data.readSyncsafeInt();
/*
* Evaluate flags
*/
if (version == 2) { // %(unsynchronisation)(compression)000000
unsynchronization = (flags & 0x80) != 0;
compression = (flags & 0x40) != 0;
} else { // %(unsynchronisation)(extendedHeader)(experimentalIndicator)(version == 3 ? 0 : footerPresent)0000
unsynchronization = (flags & 0x80) != 0;
/*
* Extended Header
*/
if ((flags & 0x40) != 0) {
if (version == 3) {
/*
* Extended header size: $xx xx xx xx (6 or 10 if CRC data present)
* In version 3, the size excludes itself.
*/
int extendedHeaderSize = data.readInt();
/*
* Extended Flags: $xx xx (skip)
*/
data.readByte(); // flags...
data.readByte(); // more flags...
/*
* Size of padding: $xx xx xx xx
*/
paddingSize = data.readInt();
/*
* consume the rest
*/
data.skipFully(extendedHeaderSize - 6);
} else {
/*
* Extended header size: 4 * %0xxxxxxx (sync-save integer)
* In version 4, the size includes itself.
*/
int extendedHeaderSize = data.readSyncsafeInt();
/*
* consume the rest
*/
data.skipFully(extendedHeaderSize - 4);
}
}
/*
* Footer Present
*/
if (version >= 4 && (flags & 0x10) != 0) { // footer present
footerSize = 10;
totalTagSize += 10;
}
}
headerSize = (int) (input.getPosition() - startPosition);
}
public ID3v2TagBody tagBody(InputStream input) throws IOException, ID3v2Exception {
if (compression) {
throw new ID3v2Exception("Tag compression is not supported");
}
if (version < 4 && unsynchronization) {
byte[] bytes = new ID3v2DataInput(input).readFully(totalTagSize - headerSize);
boolean ff = false;
int len = 0;
for (byte b : bytes) {
if (!ff || b != 0) {
bytes[len++] = b;
}
ff = (b == 0xFF);
}
return new ID3v2TagBody(new ByteArrayInputStream(bytes, 0, len), headerSize, len, this);
} else {
return new ID3v2TagBody(input, headerSize, totalTagSize - headerSize - footerSize, this);
}
}
public int getVersion() {
return version;
}
public int getRevision() {
return revision;
}
public int getTotalTagSize() {
return totalTagSize;
}
public boolean isUnsynchronization() {
return unsynchronization;
}
public boolean isCompression() {
return compression;
}
public int getHeaderSize() {
return headerSize;
}
public int getFooterSize() {
return footerSize;
}
public int getPaddingSize() {
return paddingSize;
}
@Override
public String toString() {
return String.format("%s[version=%s, totalTagSize=%d]", getClass().getSimpleName(), version, totalTagSize);
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
public class MP3Exception extends Exception {
private static final long serialVersionUID = 1L;
public MP3Exception(String message) {
super(message);
}
}
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
public class MP3Frame {
static final class CRC16 {
private short crc = (short) 0xFFFF;
public void update(int value, int length) {
int mask = 1 << (length - 1);
do {
if (((crc & 0x8000) == 0) ^ ((value & mask) == 0)) {
crc <<= 1;
crc ^= 0x8005;
} else {
crc <<= 1;
}
} while ((mask >>>= 1) != 0);
}
public void update(byte value) {
update(value, 8);
}
public short getValue() {
return crc;
}
public void reset() {
crc = (short) 0xFFFF;
}
}
public static class Header {
private static final int MPEG_LAYER_RESERVED = 0;
private static final int MPEG_VERSION_RESERVED = 1;
private static final int MPEG_BITRATE_FREE = 0;
private static final int MPEG_BITRATE_RESERVED = 15;
private static final int MPEG_FRQUENCY_RESERVED = 3;
// [frequency][version]
private static final int[][] FREQUENCIES = new int[][] {
// 2.5 reserved 2 1
{ 11025, -1, 22050, 44100 },
{ 12000, -1, 24000, 48000 },
{ 8000, -1, 16000, 32000 },
{ -1, -1, -1, -1 } // reserved
};
// [bitrate][version,layer]
private static final int[][] BITRATES = new int[][] {
{ 0, 0, 0, 0, 0 }, // free
{ 32000, 32000, 32000, 32000, 8000 },
{ 64000, 48000, 40000, 48000, 16000 },
{ 96000, 56000, 48000, 56000, 24000 },
{ 128000, 64000, 56000, 64000, 32000 },
{ 160000, 80000, 64000, 80000, 40000 },
{ 192000, 96000, 80000, 96000, 48000 },
{ 224000, 112000, 96000, 112000, 56000 },
{ 256000, 128000, 112000, 128000, 64000 },
{ 288000, 160000, 128000, 144000, 80000 },
{ 320000, 192000, 160000, 160000, 96000 },
{ 352000, 224000, 192000, 176000, 112000 },
{ 384000, 256000, 224000, 192000, 128000 },
{ 416000, 320000, 256000, 224000, 144000 },
{ 448000, 384000, 320000, 256000, 160000 },
{ -1, -1, -1, -1, -1 } // reserved
};
// [version][layer]
private static final int[][] BITRATES_COLUMN = new int[][] {
// reserved III II I
{ -1, 4, 4, 3 }, // 2.5
{ -1, -1, -1, -1 }, // reserved
{ -1, 4, 4, 3 }, // 2
{ -1, 2, 1, 0 } // 1
};
// [version][layer]
private static final int[][] SIZE_COEFFICIENTS = new int[][] {
// reserved III II I
{ -1, 72, 144, 12 }, // 2.5
{ -1, -1, -1, -1 }, // reserved
{ -1, 72, 144, 12 }, // 2
{ -1, 144, 144, 12 } // 1
};
// [layer]
private static final int[] SLOT_SIZES = new int[] {
// reserved III II I
-1, 1, 1, 4
};
// [channelMode][version]
private static final int[][] SIDE_INFO_SIZES = new int[][] {
// 2.5 reserved 2 1
{ 17, -1, 17, 32 }, // stereo
{ 17, -1, 17, 32 }, // joint stereo
{ 17, -1, 17, 32 }, // dual channel
{ 9, -1, 9, 17 }, // mono
};
public static final int MPEG_LAYER_1 = 3;
public static final int MPEG_LAYER_2 = 2;
public static final int MPEG_LAYER_3 = 1;
public static final int MPEG_VERSION_1 = 3;
public static final int MPEG_VERSION_2 = 2;
public static final int MPEG_VERSION_2_5 = 0;
public static final int MPEG_CHANNEL_MODE_MONO = 3;
public static final int MPEG_PROTECTION_CRC = 0;
private final int version;
private final int layer;
private final int frequency;
private final int bitrate;
private final int channelMode;
private final int padding;
private final int protection;
public Header(int b1, int b2, int b3) throws MP3Exception {
version = b1 >> 3 & 0x3;
if (version == MPEG_VERSION_RESERVED) {
throw new MP3Exception("Reserved version");
}
layer = b1 >> 1 & 0x3;
if (layer == MPEG_LAYER_RESERVED) {
throw new MP3Exception("Reserved layer");
}
bitrate = b2 >> 4 & 0xF;
if (bitrate == MPEG_BITRATE_RESERVED) {
throw new MP3Exception("Reserved bitrate");
}
if (bitrate == MPEG_BITRATE_FREE) {
throw new MP3Exception("Free bitrate");
}
frequency = b2 >> 2 & 0x3;
if (frequency == MPEG_FRQUENCY_RESERVED) {
throw new MP3Exception("Reserved frequency");
}
channelMode = b3 >> 6 & 0x3;
padding = b2 >> 1 & 0x1;
protection = b1 & 0x1;
int minFrameSize = 4;
if (protection == MPEG_PROTECTION_CRC) {
minFrameSize += 2;
}
if (layer == MPEG_LAYER_3) {
minFrameSize += getSideInfoSize();
}
if (getFrameSize() < minFrameSize) {
throw new MP3Exception("Frame size must be at least " + minFrameSize);
}
}
public int getVersion() {
return version;
}
public int getLayer() {
return layer;
}
public int getFrequency() {
return FREQUENCIES[frequency][version];
}
public int getChannelMode() {
return channelMode;
}
public int getProtection() {
return protection;
}
public int getSampleCount() {
if (layer == MPEG_LAYER_1) {
return 384;
} else { // TODO correct?
return 1152;
}
}
public int getFrameSize() {
return ((SIZE_COEFFICIENTS[version][layer] * getBitrate() / getFrequency()) + padding) * SLOT_SIZES[layer];
}
public int getBitrate() {
return BITRATES[bitrate][BITRATES_COLUMN[version][layer]];
}
public int getDuration() {
return (int)getTotalDuration(getFrameSize());
}
public long getTotalDuration(long totalSize) {
long duration = 1000L * (getSampleCount() * totalSize) / (getFrameSize() * getFrequency());
if (getVersion() != MPEG_VERSION_1 && getChannelMode() == MPEG_CHANNEL_MODE_MONO) {
duration /= 2;
}
return duration;
}
public boolean isCompatible(Header header) {
return layer == header.layer && version == header.version && frequency == header.frequency && channelMode == header.channelMode;
}
public int getSideInfoSize() {
return SIDE_INFO_SIZES[channelMode][version];
}
public int getXingOffset() {
return 4 + getSideInfoSize();
}
public int getVBRIOffset() {
return 4 + 32;
}
}
private final byte[] bytes;
private final Header header;
MP3Frame(Header header, byte[] bytes) {
this.header = header;
this.bytes = bytes;
}
boolean isChecksumError() {
if (header.getProtection() == Header.MPEG_PROTECTION_CRC) {
if (header.getLayer() == Header.MPEG_LAYER_3) {
CRC16 crc16 = new CRC16();
crc16.update(bytes[2]);
crc16.update(bytes[3]);
// skip crc bytes 4+5
int sideInfoSize = header.getSideInfoSize();
for (int i = 0; i < sideInfoSize; i++) {
crc16.update(bytes[6 + i]);
}
int crc = ((bytes[4] & 0xFF) << 8) | (bytes[5] & 0xFF);
return crc != crc16.getValue();
}
}
return false;
}
public int getSize() {
return bytes.length;
}
public Header getHeader() {
return header;
}
boolean isXingFrame() {
int xingOffset = header.getXingOffset();
if (bytes.length < xingOffset + 12) { // minimum Xing header size == 12
return false;
}
if (xingOffset < 0 || bytes.length < xingOffset + 8) {
return false;
}
if (bytes[xingOffset] == 'X' && bytes[xingOffset + 1] == 'i' && bytes[xingOffset + 2] == 'n' && bytes[xingOffset + 3] == 'g') {
return true;
}
if (bytes[xingOffset] == 'I' && bytes[xingOffset + 1] == 'n' && bytes[xingOffset + 2] == 'f' && bytes[xingOffset + 3] == 'o') {
return true;
}
return false;
}
boolean isVBRIFrame() {
int vbriOffset = header.getVBRIOffset();
if (bytes.length < vbriOffset + 26) { // minimum VBRI header size == 26
return false;
}
return bytes[vbriOffset] == 'V' && bytes[vbriOffset + 1] == 'B' && bytes[vbriOffset + 2] == 'R' && bytes[vbriOffset + 3] == 'I';
}
public int getNumberOfFrames() {
if (isXingFrame()) {
int xingOffset = header.getXingOffset();
byte flags = bytes[xingOffset + 7];
if ((flags & 0x01) != 0) {
return ((bytes[xingOffset + 8] & 0xFF) << 24) |
((bytes[xingOffset + 9] & 0xFF) << 16) |
((bytes[xingOffset + 10] & 0xFF) << 8) |
( bytes[xingOffset + 11] & 0xFF);
}
} else if (isVBRIFrame()) {
int vbriOffset = header.getVBRIOffset();
return ((bytes[vbriOffset + 14] & 0xFF) << 24) |
((bytes[vbriOffset + 15] & 0xFF) << 16) |
((bytes[vbriOffset + 16] & 0xFF) << 8) |
( bytes[vbriOffset + 17] & 0xFF);
}
return -1;
}
}
\ No newline at end of file
/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.telegram.android.audioinfo.mp3;
import org.telegram.android.audioinfo.util.PositionInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
public class MP3Input extends PositionInputStream {
public MP3Input(InputStream delegate) throws IOException {
super(delegate);
}
public MP3Input(InputStream delegate, long position) {
super(delegate, position);
}
public final void readFully(byte b[], int off, int len) throws IOException {
int total = 0;
while (total < len) {
int current = read(b, off + total, len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
}
public void skipFully(long len) throws IOException {
long total = 0;
while (total < len) {
long current = skip(len - total);
if (current > 0) {
total += current;
} else {
throw new EOFException();
}
}
}
public String toString() {
return "mp3[pos=" + getPosition() + "]";
}
}
......@@ -14,9 +14,10 @@
* limitations under the License.
*/
package android.support.v7.util;
package org.telegram.android.support.util;
import java.lang.reflect.Array;
import java.util.Arrays;
/**
* A Sorted list implementation that can keep items in order and also notify for changes in the
......@@ -418,6 +419,19 @@ public class SortedList<T> {
mSize++;
}
/**
* Removes all items from the SortedList.
*/
public void clear() {
if (mSize == 0) {
return;
}
final int prevSize = mSize;
Arrays.fill(mData, 0, prevSize, null);
mSize = 0;
mCallback.onRemoved(0, prevSize);
}
/**
* The class that controls the behavior of the {@link SortedList}.
* <p>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment