aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/github/daneren2005/dsub/util/tags/ID3v2File.java
blob: 69668475105f766ebb622096b7101d7c58b2a596 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/*
 * Copyright (C) 2013 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 github.daneren2005.dsub.util.tags;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;


public class ID3v2File extends Common {
	private static int ID3_ENC_LATIN   = 0x00;
	private static int ID3_ENC_UTF16LE = 0x01;
	private static int ID3_ENC_UTF16BE = 0x02;
	private static int ID3_ENC_UTF8    = 0x03;
	
	public ID3v2File() {
	}
	
	public HashMap getTags(RandomAccessFile s) throws IOException {
		HashMap tags = new HashMap();
		
		final int v2hdr_len = 10;
		byte[] v2hdr = new byte[v2hdr_len];
		
		// read the whole 10 byte header into memory
		s.seek(0);
		s.read(v2hdr);
		
		int id3v   = ((b2be32(v2hdr,0))) & 0xFF;   // swapped ID3\04 -> ver. ist the first byte
		int v3len  = ((b2be32(v2hdr,6)));          // total size EXCLUDING the this 10 byte header
		v3len      = ((v3len & 0x7f000000) >> 3) | // for some funky reason, this is encoded as 7*4 bits
		             ((v3len & 0x007f0000) >> 2) |
		             ((v3len & 0x00007f00) >> 1) |
		             ((v3len & 0x0000007f) >> 0) ;
		
		// debug(">> tag version ID3v2."+id3v);
		// debug(">> LEN= "+v3len+" // "+v3len);
		
		// we should already be at the first frame
		// so we can start the parsing right now
		tags = parse_v3_frames(s, v3len);
		tags.put("_hdrlen", v3len+v2hdr_len);
		return tags;
	}
	
	/* Parses all ID3v2 frames at the current position up until payload_len
	** bytes were read
	*/
	public HashMap parse_v3_frames(RandomAccessFile s, long payload_len) throws IOException {
		HashMap tags = new HashMap();
		byte[] frame   = new byte[10]; // a frame header is always 10 bytes
		long bread     = 0;            // total amount of read bytes
		
		while(bread < payload_len) {
			bread += s.read(frame);
			String framename = new String(frame, 0, 4);
			int slen = b2be32(frame, 4);
			
			/* Abort on silly sizes */
			if(slen < 1 || slen > 524288)
				break;
			
			byte[] xpl = new byte[slen];
			bread += s.read(xpl);

			if(framename.substring(0,1).equals("T")) {
				String[] nmzInfo = normalizeTaginfo(framename, xpl);

				for(int i = 0; i < nmzInfo.length; i += 2) {
					String oggKey = nmzInfo[i];
					String decPld = nmzInfo[i + 1];

					if (oggKey.length() > 0 && !tags.containsKey(oggKey)) {
						addTagEntry(tags, oggKey, decPld);
					}
				}
			}
			else if(framename.equals("RVA2")) {
				//
			}
			
		}
		return tags;
	}
	
	/* Converts ID3v2 sillyframes to OggNames */
	private String[] normalizeTaginfo(String k, byte[] v) {
		String[] rv = new String[] {"",""};
		HashMap lu = new HashMap<String, String>();
		lu.put("TIT2", "TITLE");
		lu.put("TALB", "ALBUM");
		lu.put("TPE1", "ARTIST");
		
		if(lu.containsKey(k)) {
			/* A normal, known key: translate into Ogg-Frame name */
			rv[0] = (String)lu.get(k);
			rv[1] = getDecodedString(v);
		}
		else if(k.equals("TXXX")) {
			/* A freestyle field, ieks! */
			String txData[] = getDecodedString(v).split(Character.toString('\0'), 2);
			/* Check if we got replaygain info in key\0value style */
			if(txData.length == 2) {
				if(txData[0].matches("^(?i)REPLAYGAIN_(ALBUM|TRACK)_GAIN$")) {
					rv[0] = txData[0].toUpperCase(); /* some tagwriters use lowercase for this */
					rv[1] = txData[1];
				} else {
					// Check for replaygain tags just thrown randomly in field
					int nextStartIndex = 1;
					int startName = txData[1].toLowerCase(Locale.US).indexOf("replaygain_");
					ArrayList<String> parts = new ArrayList<String>();
					while(startName != -1) {
						int endName = txData[1].indexOf((char) 0, startName);
						if(endName != -1) {
							parts.add(txData[1].substring(startName, endName).toUpperCase());
							int endValue = txData[1].indexOf((char) 0, endName + 1);
							if(endValue != -1) {
								parts.add(txData[1].substring(endName + 1, endValue));
								nextStartIndex = endValue + 1;
							} else {
								break;
							}
						} else {
							break;
						}

						startName = txData[1].toLowerCase(Locale.US).indexOf("replaygain_", nextStartIndex);
					}

					if(parts.size() > 0) {
						rv = new String[parts.size()];
						rv = parts.toArray(rv);
					}
				}
			}
		}
		
		return rv;
	}
	
	/* Converts a raw byte-stream text into a java String */
	private String getDecodedString(byte[] raw) {
		int encid = raw[0] & 0xFF;
		int len   = raw.length;
		String v  = "";
		try {
			if(encid == ID3_ENC_LATIN) {
				v = new String(raw, 1, len-1, "ISO-8859-1");
			}
			else if (encid == ID3_ENC_UTF8) {
				v = new String(raw, 1, len-1, "UTF-8");
			}
			else if (encid == ID3_ENC_UTF16LE) {
				v = new String(raw, 3, len-3, "UTF-16LE");
			}
			else if (encid == ID3_ENC_UTF16BE) {
				v = new String(raw, 3, len-3, "UTF-16BE");
			}
		} catch(Exception e) {}
		return v;
	}
	
}