PageRenderTime 48ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/src/br/com/carlosrafaelgn/fplay/playback/MetadataExtractor.java

https://gitlab.com/madamovic-bg/FPlayAndroid
Java | 321 lines | 238 code | 9 blank | 74 comment | 108 complexity | c68cc6edeb11df2ae209bfd861a9fdc8 MD5 | raw file
  1. //
  2. // FPlayAndroid is distributed under the FreeBSD License
  3. //
  4. // Copyright (c) 2013-2014, Carlos Rafael Gimenes das Neves
  5. // All rights reserved.
  6. //
  7. // Redistribution and use in source and binary forms, with or without
  8. // modification, are permitted provided that the following conditions are met:
  9. //
  10. // 1. Redistributions of source code must retain the above copyright notice, this
  11. // list of conditions and the following disclaimer.
  12. // 2. Redistributions in binary form must reproduce the above copyright notice,
  13. // this list of conditions and the following disclaimer in the documentation
  14. // and/or other materials provided with the distribution.
  15. //
  16. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  17. // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  20. // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. //
  27. // The views and conclusions contained in the software and documentation are those
  28. // of the authors and should not be interpreted as representing official policies,
  29. // either expressed or implied, of the FreeBSD Project.
  30. //
  31. // https://github.com/carlosrafaelgn/FPlayAndroid
  32. //
  33. package br.com.carlosrafaelgn.fplay.playback;
  34. import java.io.IOException;
  35. import java.io.RandomAccessFile;
  36. import java.util.Locale;
  37. import br.com.carlosrafaelgn.fplay.list.FileSt;
  38. public final class MetadataExtractor {
  39. private static final int FIELD_COUNT = 6;
  40. public static final int TITLE = 0;
  41. public static final int ARTIST = 1;
  42. public static final int ALBUM = 2;
  43. public static final int TRACK = 3;
  44. public static final int YEAR = 4;
  45. public static final int LENGTH = 5;
  46. private static final int ALL_B = 0x3f;
  47. private static final int ALL_BUT_LENGTH_B = 0x1f;
  48. private static final int TITLE_B = 0x01;
  49. private static final int ARTIST_B = 0x02;
  50. private static final int ALBUM_B = 0x04;
  51. private static final int TRACK_B = 0x08;
  52. private static final int YEAR_B = 0x10;
  53. private static final int LENGTH_B = 0x20;
  54. private static String readV2Frame(RandomAccessFile f, int frameSize, byte[][] tmpPtr) throws IOException {
  55. if (frameSize < 2) {
  56. f.skipBytes(frameSize);
  57. return null;
  58. }
  59. final int encoding = f.read();
  60. frameSize--; //discount the encoding
  61. if (encoding < 0 || encoding > 3) {
  62. f.skipBytes(frameSize);
  63. return null;
  64. }
  65. byte[] tmp = tmpPtr[0];
  66. if (frameSize > tmp.length) {
  67. tmp = new byte[frameSize + 16];
  68. tmpPtr[0] = tmp;
  69. }
  70. f.readFully(tmp, 0, frameSize);
  71. //according to http://developer.android.com/reference/java/nio/charset/Charset.html
  72. //the following charsets are ALWAYS available:
  73. //ISO-8859-1
  74. //US-ASCII
  75. //UTF-16
  76. //UTF-16BE
  77. //UTF-16LE
  78. //UTF-8
  79. String ret = null;
  80. switch (encoding) {
  81. case 0: //ISO-8859-1
  82. ret = new String(tmp, 0, frameSize, "ISO-8859-1");
  83. break;
  84. case 1: //UCS-2 (UTF-16 encoded Unicode with BOM), in ID3v2.2 and ID3v2.3
  85. case 2: //UTF-16BE encoded Unicode without BOM, in ID3v2.4
  86. ret = new String(tmp, 0, frameSize, "UTF-16");
  87. break;
  88. case 3: //UTF-8 encoded Unicode, in ID3v2.4
  89. //BOM
  90. ret = ((tmp[0] == (byte)0xef && tmp[1] == (byte)0xbb && tmp[2] == (byte)0xbf) ?
  91. new String(tmp, 3, frameSize - 3, "UTF-8") :
  92. new String(tmp, 0, frameSize, "UTF-8"));
  93. break;
  94. }
  95. return ((ret != null && ret.length() == 0) ? null : ret);
  96. }
  97. private static String[] extractID3v1(RandomAccessFile f, int found, String[] fields, byte[] tmp) {
  98. try {
  99. f.seek(f.length() - 128);
  100. f.readFully(tmp, 0, 128);
  101. if (tmp[0] != 0x54 ||
  102. tmp[1] != 0x41 ||
  103. tmp[2] != 0x47) //TAG
  104. return fields;
  105. //struct _ID3v1 {
  106. //public:
  107. // char title[30];
  108. // char artist[30];
  109. // char album[30];
  110. // char year[4];
  111. // char comment[28];
  112. // unsigned char zeroByte; //If a track number is stored, this byte contains a binary 0.
  113. // unsigned char track; //The number of the track on the album, or 0. Invalid, if previous byte is not a binary 0.
  114. // unsigned char genre; //Index in a list of genres, or 255
  115. //} tag;
  116. int i, c;
  117. if ((found & TITLE_B) == 0) {
  118. i = 3;
  119. c = 0;
  120. while (c < 30 && tmp[i] != 0) {
  121. c++;
  122. i++;
  123. }
  124. if (c != 0)
  125. fields[MetadataExtractor.TITLE] = new String(tmp, 3, c, "ISO-8859-1");
  126. }
  127. if ((found & ARTIST_B) == 0) {
  128. i = 3 + 30;
  129. c = 0;
  130. while (c < 30 && tmp[i] != 0) {
  131. c++;
  132. i++;
  133. }
  134. if (c != 0)
  135. fields[MetadataExtractor.ARTIST] = new String(tmp, 3 + 30, c, "ISO-8859-1");
  136. }
  137. if ((found & ALBUM_B) == 0) {
  138. i = 3 + 30 + 30;
  139. c = 0;
  140. while (c < 30 && tmp[i] != 0) {
  141. c++;
  142. i++;
  143. }
  144. if (c != 0)
  145. fields[MetadataExtractor.ALBUM] = new String(tmp, 3 + 30 + 30, c, "ISO-8859-1");
  146. }
  147. if ((found & YEAR_B) == 0) {
  148. i = 3 + 30 + 30 + 30;
  149. c = 0;
  150. while (c < 4 && tmp[i] != 0) {
  151. c++;
  152. i++;
  153. }
  154. if (c != 0)
  155. fields[MetadataExtractor.YEAR] = new String(tmp, 3 + 30 + 30 + 30, c, "ISO-8859-1");
  156. }
  157. if ((found & TRACK_B) == 0 && tmp[128 - 3] == 0 && tmp[128 - 2] != 0)
  158. fields[MetadataExtractor.TRACK] = Integer.toString((int)tmp[128 - 2] & 0xff);
  159. } catch (Throwable ex) {
  160. //ignore all exceptions while reading ID3v1, in favor of
  161. //everything that has already been read in ID3v2
  162. }
  163. return fields;
  164. }
  165. private static String[] extractID3v2Andv1(RandomAccessFile f, byte[][] tmpPtr) throws IOException {
  166. //struct _ID3v2TagHdr {
  167. //public:
  168. // unsigned int hdr;
  169. // unsigned char hdrRev;
  170. // unsigned char flags;
  171. // unsigned char sizeBytes[4];
  172. //} tagV2Hdr;
  173. //readInt() reads a big-endian 32-bit integer
  174. final int hdr = f.readInt();
  175. if ((hdr & 0xffffff00) != 0x49443300) //ID3
  176. return null;
  177. final int hdrRev = f.read();
  178. final int flags = f.read();
  179. final int sizeBytes0 = f.read();
  180. final int sizeBytes1 = f.read();
  181. final int sizeBytes2 = f.read();
  182. final int sizeBytes3 = f.read();
  183. int size = ((flags & 0x10) != 0 ? 10 : 0) + //footer presence flag
  184. (
  185. (sizeBytes3 & 0x7f) |
  186. ((sizeBytes2 & 0x7f) << 7) |
  187. ((sizeBytes1 & 0x7f) << 14) |
  188. ((sizeBytes0 & 0x7f) << 21)
  189. );
  190. if ((hdr & 0xff) > 2 || hdrRev != 0) { //only rev 3 or greater supported
  191. //http://id3.org/id3v2.3.0
  192. //http://id3.org/id3v2.4.0-structure
  193. //http://id3.org/id3v2.4.0-frames
  194. final String[] fields = new String[FIELD_COUNT];
  195. int found = 0;
  196. while (size > 0 && found != ALL_B) {
  197. //struct _ID3v2FrameHdr {
  198. //public:
  199. // unsigned int id;
  200. // unsigned int size;
  201. // unsigned short flags;
  202. //} frame;
  203. final int frameId = f.readInt();
  204. final int frameSize = f.readInt();
  205. //skip the flags
  206. f.readShort();
  207. if (frameId == 0 || frameSize <= 0 || frameSize > size)
  208. break;
  209. switch (frameId) {
  210. case 0x54495432: //title - TIT2
  211. if ((found & TITLE_B) == 0) {
  212. fields[TITLE] = readV2Frame(f, frameSize, tmpPtr);
  213. if (fields[TITLE] != null)
  214. found |= TITLE_B;
  215. } else {
  216. f.skipBytes(frameSize);
  217. }
  218. break;
  219. case 0x54504531: //artist - TPE1
  220. if ((found & ARTIST_B) == 0) {
  221. fields[ARTIST] = readV2Frame(f, frameSize, tmpPtr);
  222. if (fields[ARTIST] != null)
  223. found |= ARTIST_B;
  224. } else {
  225. f.skipBytes(frameSize);
  226. }
  227. break;
  228. case 0x54414c42: //album - TALB
  229. if ((found & ALBUM_B) == 0) {
  230. fields[ALBUM] = readV2Frame(f, frameSize, tmpPtr);
  231. if (fields[ALBUM] != null)
  232. found |= ALBUM_B;
  233. } else {
  234. f.skipBytes(frameSize);
  235. }
  236. break;
  237. case 0x5452434b: //track - TRCK
  238. if ((found & TRACK_B) == 0) {
  239. fields[TRACK] = readV2Frame(f, frameSize, tmpPtr);
  240. if (fields[TRACK] != null)
  241. found |= TRACK_B;
  242. } else {
  243. f.skipBytes(frameSize);
  244. }
  245. break;
  246. case 0x54594552: //year - TYER
  247. if ((found & YEAR_B) == 0) {
  248. fields[YEAR] = readV2Frame(f, frameSize, tmpPtr);
  249. if (fields[YEAR] != null)
  250. found |= YEAR_B;
  251. } else {
  252. f.skipBytes(frameSize);
  253. }
  254. break;
  255. case 0x54445243: //Recording time - TDRC
  256. if ((found & YEAR_B) == 0) {
  257. fields[YEAR] = readV2Frame(f, frameSize, tmpPtr);
  258. if (fields[YEAR] != null) {
  259. if (fields[YEAR].length() > 4)
  260. fields[YEAR] = fields[YEAR].substring(0, 4);
  261. found |= YEAR_B;
  262. }
  263. } else {
  264. f.skipBytes(frameSize);
  265. }
  266. break;
  267. case 0x544c454e: //length - TLEN
  268. if ((found & LENGTH_B) == 0) {
  269. fields[LENGTH] = readV2Frame(f, frameSize, tmpPtr);
  270. if (fields[LENGTH] != null)
  271. found |= LENGTH_B;
  272. } else {
  273. f.skipBytes(frameSize);
  274. }
  275. break;
  276. default:
  277. f.skipBytes(frameSize);
  278. break;
  279. }
  280. size -= (10 + frameSize);
  281. }
  282. //try to extract ID3v1 only if there are any blank fields
  283. return (((found & ALL_BUT_LENGTH_B) != ALL_BUT_LENGTH_B) ? extractID3v1(f, found, fields, tmpPtr[0]) : fields);
  284. }
  285. return null;
  286. }
  287. public static String[] extract(FileSt file, byte[][] tmpPtr) {
  288. int i = file.path.lastIndexOf('.');
  289. if (i < 0)
  290. return null;
  291. final String ext = file.path.substring(i + 1).toLowerCase(Locale.US);
  292. //the only two formats supported for now... I hope to add ogg soon ;)
  293. if (!ext.equals("mp3") && !ext.equals("aac"))
  294. return null;
  295. RandomAccessFile f = null;
  296. try {
  297. f = ((file.file != null) ? new RandomAccessFile(file.file, "r") : new RandomAccessFile(file.path, "r"));
  298. return extractID3v2Andv1(f, tmpPtr);
  299. } catch (Throwable ex) {
  300. ex.printStackTrace();
  301. } finally {
  302. if (f != null) {
  303. try {
  304. f.close();
  305. } catch (Throwable ex2) {
  306. ex2.printStackTrace();
  307. }
  308. }
  309. }
  310. return null;
  311. }
  312. }