~ubuntu-branches/ubuntu/utopic/libjaudiotagger-java/utopic

« back to all changes in this revision

Viewing changes to src/org/jaudiotagger/tag/id3/ID3v22Frame.java

  • Committer: Bazaar Package Importer
  • Author(s): Damien Raude-Morvan
  • Date: 2011-04-28 23:52:43 UTC
  • mfrom: (3.1.4 sid)
  • Revision ID: james.westby@ubuntu.com-20110428235243-pzalvw6lncis3ukf
Tags: 2.0.3-1
* d/control: Drop Depends on default-jre per Debian Java Policy as its
  a library package.
* d/watch: Fix to directly monitor SVN tags.
* Switch to 3.0 (quilt) format.
* Bump Standards-Version to 3.9.2 (no changes needed).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 *  MusicTag Copyright (C)2003,2004
3
 
 *
4
 
 *  This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
5
 
 *  General Public  License as published by the Free Software Foundation; either version 2.1 of the License,
6
 
 *  or (at your option) any later version.
7
 
 *
8
 
 *  This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
9
 
 *  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
 
 *  See the GNU Lesser General Public License for more details.
11
 
 *
12
 
 *  You should have received a copy of the GNU Lesser General Public License along with this library; if not,
13
 
 *  you can get a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software
14
 
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
 
 */
16
 
package org.jaudiotagger.tag.id3;
17
 
 
18
 
import org.jaudiotagger.audio.generic.Utils;
19
 
import org.jaudiotagger.audio.mp3.MP3File;
20
 
import org.jaudiotagger.tag.EmptyFrameException;
21
 
import org.jaudiotagger.tag.InvalidFrameException;
22
 
import org.jaudiotagger.tag.InvalidFrameIdentifierException;
23
 
import org.jaudiotagger.tag.id3.framebody.AbstractID3v2FrameBody;
24
 
import org.jaudiotagger.tag.id3.framebody.FrameBodyDeprecated;
25
 
import org.jaudiotagger.tag.id3.framebody.FrameBodyUnsupported;
26
 
 
27
 
import java.io.ByteArrayOutputStream;
28
 
import java.io.IOException;
29
 
import java.math.BigInteger;
30
 
import java.nio.ByteBuffer;
31
 
import java.util.logging.Level;
32
 
import java.util.regex.Matcher;
33
 
import java.util.regex.Pattern;
34
 
 
35
 
/**
36
 
 * Represents an ID3v2.2 frame.
37
 
 *
38
 
 * @author : Paul Taylor
39
 
 * @author : Eric Farng
40
 
 * @version $Id: ID3v22Frame.java,v 1.34 2009/11/12 15:42:57 paultaylor Exp $
41
 
 */
42
 
public class ID3v22Frame extends AbstractID3v2Frame
43
 
{
44
 
    private static Pattern validFrameIdentifier = Pattern.compile("[A-Z][0-9A-Z]{2}");
45
 
 
46
 
    protected static final int FRAME_ID_SIZE = 3;
47
 
    protected static final int FRAME_SIZE_SIZE = 3;
48
 
    protected static final int FRAME_HEADER_SIZE = FRAME_ID_SIZE + FRAME_SIZE_SIZE;
49
 
 
50
 
    public ID3v22Frame()
51
 
    {
52
 
 
53
 
    }
54
 
 
55
 
    /**
56
 
     * Creates a new ID3v22 Frame with given body
57
 
     *
58
 
     * @param body New body and frame is based on this
59
 
     */
60
 
    public ID3v22Frame(AbstractID3v2FrameBody body)
61
 
    {
62
 
        super(body);
63
 
    }
64
 
 
65
 
    /**
66
 
     * Creates a new ID3v22 Frame of type identifier.
67
 
     * <p/>
68
 
     * An empty body of the correct type will be automatically created. This constructor should be used when wish to
69
 
     * create a new frame from scratch using user values
70
 
     * @param identifier
71
 
     */
72
 
    @SuppressWarnings("unchecked")
73
 
    public ID3v22Frame(String identifier)
74
 
    {
75
 
 
76
 
        logger.info("Creating empty frame of type" + identifier);
77
 
        String bodyIdentifier = identifier;
78
 
        this.identifier = identifier;
79
 
 
80
 
        //If dealing with v22 identifier (Note this constructor is used by all three tag versions)
81
 
        if (ID3Tags.isID3v22FrameIdentifier(bodyIdentifier))
82
 
        {
83
 
            //Does it have its own framebody (PIC,CRM) or are we using v23/v24 body (the normal case)
84
 
            if (ID3Tags.forceFrameID22To23(bodyIdentifier) != null)
85
 
            {
86
 
                //Do not convert
87
 
            }
88
 
            //TODO Improve messy fix for datetime
89
 
            //TODO need to check in case v22 body does exist before using V23 body(e.g PIC)
90
 
            else
91
 
            if ((bodyIdentifier.equals(ID3v22Frames.FRAME_ID_V2_TYER)) || (bodyIdentifier.equals(ID3v22Frames.FRAME_ID_V2_TIME)))
92
 
            {
93
 
                bodyIdentifier = ID3v24Frames.FRAME_ID_YEAR;
94
 
            }
95
 
            // Have to check for v22 because most don't have own body they use v23 or v24
96
 
            // body to hold the data, the frame is identified by its identifier, the body identifier
97
 
            // is just to create a body suitable for writing the data to
98
 
            else if (ID3Tags.isID3v22FrameIdentifier(bodyIdentifier))
99
 
            {
100
 
                bodyIdentifier = ID3Tags.convertFrameID22To23(bodyIdentifier);
101
 
            }
102
 
        }
103
 
 
104
 
        // Use reflection to map id to frame body, which makes things much easier
105
 
        // to keep things up to date.
106
 
        try
107
 
        {
108
 
            Class<AbstractID3v2FrameBody> c = (Class<AbstractID3v2FrameBody>) Class.forName("org.jaudiotagger.tag.id3.framebody.FrameBody" + bodyIdentifier);
109
 
            frameBody = c.newInstance();
110
 
        }
111
 
        catch (ClassNotFoundException cnfe)
112
 
        {
113
 
            logger.log(Level.SEVERE, cnfe.getMessage(), cnfe);
114
 
            frameBody = new FrameBodyUnsupported(identifier);
115
 
        }
116
 
        //Instantiate Interface/Abstract should not happen
117
 
        catch (InstantiationException ie)
118
 
        {
119
 
            logger.log(Level.SEVERE, ie.getMessage(), ie);
120
 
            throw new RuntimeException(ie);
121
 
        }
122
 
        //Private Constructor shouild not happen
123
 
        catch (IllegalAccessException iae)
124
 
        {
125
 
            logger.log(Level.SEVERE, iae.getMessage(), iae);
126
 
            throw new RuntimeException(iae);
127
 
        }
128
 
        frameBody.setHeader(this);
129
 
        logger.info("Created empty frame of type" + this.identifier + "with frame body of" + bodyIdentifier);
130
 
 
131
 
    }
132
 
 
133
 
    /**
134
 
     * Copy Constructor
135
 
     * <p/>
136
 
     * Creates a new v22 frame based on another v22 frame
137
 
     * @param frame
138
 
     */
139
 
    public ID3v22Frame(ID3v22Frame frame)
140
 
    {
141
 
        super(frame);
142
 
        logger.info("Creating frame from a frame of same version");
143
 
    }
144
 
 
145
 
    private void createV22FrameFromV23Frame(ID3v23Frame frame) throws InvalidFrameException
146
 
    {
147
 
        identifier = ID3Tags.convertFrameID23To22(frame.getIdentifier());
148
 
        if (identifier != null)
149
 
        {
150
 
            logger.info("V2:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
151
 
            this.frameBody = (AbstractID3v2FrameBody) ID3Tags.copyObject(frame.getBody());
152
 
        }
153
 
        // Is it a known v3 frame which needs forcing to v2 frame e.g. APIC - PIC
154
 
        else if (ID3Tags.isID3v23FrameIdentifier(frame.getIdentifier()))
155
 
        {
156
 
            identifier = ID3Tags.forceFrameID23To22(frame.getIdentifier());
157
 
            if (identifier != null)
158
 
            {
159
 
                logger.info("V2:Force:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
160
 
                this.frameBody = this.readBody(identifier, (AbstractID3v2FrameBody) frame.getBody());
161
 
            }
162
 
            // No mechanism exists to convert it to a v22 frame
163
 
            else
164
 
            {
165
 
                throw new InvalidFrameException("Unable to convert v23 frame:" + frame.getIdentifier() + " to a v22 frame");
166
 
            }
167
 
        }
168
 
        //Deprecated frame for v23
169
 
        else if (frame.getBody() instanceof FrameBodyDeprecated)
170
 
        {
171
 
            //Was it valid for this tag version, if so try and reconstruct
172
 
            if (ID3Tags.isID3v22FrameIdentifier(frame.getIdentifier()))
173
 
            {
174
 
                this.frameBody = frame.getBody();
175
 
                identifier = frame.getIdentifier();
176
 
                logger.info("DEPRECATED:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
177
 
            }
178
 
            //or was it still deprecated, if so leave as is
179
 
            else
180
 
            {
181
 
                this.frameBody = new FrameBodyDeprecated((FrameBodyDeprecated) frame.getBody());
182
 
                identifier = frame.getIdentifier();
183
 
                logger.info("DEPRECATED:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
184
 
            }
185
 
        }
186
 
        // Unknown Frame e.g NCON
187
 
        else
188
 
        {
189
 
            this.frameBody = new FrameBodyUnsupported((FrameBodyUnsupported) frame.getBody());
190
 
            identifier = frame.getIdentifier();
191
 
            logger.info("v2:UNKNOWN:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
192
 
        }
193
 
    }
194
 
 
195
 
    /**
196
 
     * Creates a new ID3v22 Frame from another frame of a different tag version
197
 
     *
198
 
     * @param frame to construct the new frame from
199
 
     * @throws org.jaudiotagger.tag.InvalidFrameException
200
 
     */
201
 
    public ID3v22Frame(AbstractID3v2Frame frame) throws InvalidFrameException
202
 
    {
203
 
        logger.info("Creating frame from a frame of a different version");
204
 
        if (frame instanceof ID3v22Frame)
205
 
        {
206
 
            throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument");
207
 
        }
208
 
 
209
 
        // If it is a v24 frame is it possible to convert it into a v23 frame, anmd then convert from that
210
 
        if (frame instanceof ID3v24Frame)
211
 
        {
212
 
            ID3v23Frame v23Frame = new ID3v23Frame(frame);
213
 
            createV22FrameFromV23Frame(v23Frame);
214
 
        }
215
 
        //If it is a v23 frame is it possible to convert it into a v22 frame
216
 
        else if (frame instanceof ID3v23Frame)
217
 
        {
218
 
            createV22FrameFromV23Frame((ID3v23Frame) frame);
219
 
        }
220
 
        this.frameBody.setHeader(this);
221
 
        logger.info("Created frame from a frame of a different version");
222
 
    }
223
 
 
224
 
    /**
225
 
     * Creates a new ID3v22Frame datatype by reading from byteBuffer.
226
 
     *
227
 
     * @param byteBuffer to read from
228
 
     * @param loggingFilename
229
 
     * @throws org.jaudiotagger.tag.InvalidFrameException
230
 
     */
231
 
    public ID3v22Frame(ByteBuffer byteBuffer, String loggingFilename) throws InvalidFrameException
232
 
    {
233
 
        setLoggingFilename(loggingFilename);
234
 
        read(byteBuffer);
235
 
    }
236
 
 
237
 
    /**
238
 
     * Creates a new ID3v23Frame datatype by reading from byteBuffer.
239
 
     *
240
 
     * @param byteBuffer to read from
241
 
     * @deprecated use {@link #ID3v22Frame(ByteBuffer,String)} instead
242
 
     * @throws org.jaudiotagger.tag.InvalidFrameException
243
 
     */
244
 
    public ID3v22Frame(ByteBuffer byteBuffer) throws InvalidFrameException
245
 
    {
246
 
        this(byteBuffer, "");
247
 
    }
248
 
 
249
 
    /**
250
 
     * Return size of frame
251
 
     *
252
 
     * @return int size of frame
253
 
     */
254
 
    public int getSize()
255
 
    {
256
 
        return frameBody.getSize() + FRAME_HEADER_SIZE;
257
 
    }
258
 
 
259
 
    /**
260
 
     * Read frame from file.
261
 
     * Read the frame header then delegate reading of data to frame body.
262
 
     *
263
 
     * @param byteBuffer
264
 
     */
265
 
    public void read(ByteBuffer byteBuffer) throws InvalidFrameException
266
 
    {
267
 
        byte[] buffer = new byte[FRAME_ID_SIZE];
268
 
 
269
 
        if (byteBuffer.position() + FRAME_HEADER_SIZE >= byteBuffer.limit())
270
 
        {
271
 
            logger.warning("No space to find another frame:");
272
 
            throw new InvalidFrameException(" No space to find another frame");
273
 
        }
274
 
 
275
 
        // Read the FrameID Identifier
276
 
        byteBuffer.get(buffer, 0, FRAME_ID_SIZE);
277
 
        identifier = new String(buffer);
278
 
        logger.info("Read Frame from file identifier is:" + identifier);
279
 
 
280
 
        // Is this a valid identifier?
281
 
        if (!isValidID3v2FrameIdentifier(identifier))
282
 
        {
283
 
            logger.info("Invalid identifier:" + identifier);
284
 
            byteBuffer.position(byteBuffer.position() - (FRAME_ID_SIZE - 1));
285
 
            throw new InvalidFrameIdentifierException(identifier + " is not a valid ID3v2.20 frame");
286
 
        }
287
 
        //Read Frame Size (same size as Frame Id so reuse buffer)
288
 
        byteBuffer.get(buffer, 0, FRAME_SIZE_SIZE);
289
 
        frameSize = decodeSize(buffer);
290
 
        if (frameSize < 0)
291
 
        {
292
 
            throw new InvalidFrameException(identifier + " has invalid size of:" + frameSize);
293
 
        }
294
 
        else if (frameSize == 0)
295
 
        {
296
 
            //We dont process this frame or add to framemap becuase contains no useful information
297
 
            logger.warning("Empty Frame:" + identifier);
298
 
            throw new EmptyFrameException(identifier + " is empty frame");
299
 
        }
300
 
        else if (frameSize > byteBuffer.remaining())
301
 
        {
302
 
            logger.warning("Invalid Frame size larger than size before mp3 audio:" + identifier);
303
 
            throw new InvalidFrameException(identifier + " is invalid frame");
304
 
        }
305
 
        else
306
 
        {
307
 
            logger.fine("Frame Size Is:" + frameSize);
308
 
            //Convert v2.2 to v2.4 id just for reading the data
309
 
            String id = ID3Tags.convertFrameID22To24(identifier);
310
 
            if (id == null)
311
 
            {
312
 
                //OK,it may be convertable to a v.3 id even though not valid v.4
313
 
                id = ID3Tags.convertFrameID22To23(identifier);
314
 
                if (id == null)
315
 
                {
316
 
                    // Is it a valid v22 identifier so should be able to find a
317
 
                    // frame body for it.
318
 
                    if (ID3Tags.isID3v22FrameIdentifier(identifier))
319
 
                    {
320
 
                        id = identifier;
321
 
                    }
322
 
                    // Unknown so will be created as FrameBodyUnsupported
323
 
                    else
324
 
                    {
325
 
                        id = UNSUPPORTED_ID;
326
 
                    }
327
 
                }
328
 
            }
329
 
            logger.fine("Identifier was:" + identifier + " reading using:" + id);
330
 
 
331
 
            //Create Buffer that only contains the body of this frame rather than the remainder of tag
332
 
            ByteBuffer frameBodyBuffer = byteBuffer.slice();
333
 
            frameBodyBuffer.limit(frameSize);
334
 
 
335
 
            try
336
 
            {
337
 
                frameBody = readBody(id, frameBodyBuffer, frameSize);
338
 
            }
339
 
            finally
340
 
            {
341
 
                //Update position of main buffer, so no attempt is made to reread these bytes
342
 
                byteBuffer.position(byteBuffer.position() + frameSize);
343
 
            }
344
 
        }
345
 
    }
346
 
 
347
 
    /**
348
 
     * Read Frame Size, which has to be decoded
349
 
     * @param buffer
350
 
     * @return
351
 
     */
352
 
    private int decodeSize(byte[] buffer)
353
 
    {
354
 
        BigInteger bi = new BigInteger(buffer);
355
 
        int tmpSize = bi.intValue();
356
 
        if (tmpSize < 0)
357
 
        {
358
 
            logger.warning("Invalid Frame Size of:" + tmpSize + "Decoded from bin:" + Integer.toBinaryString(tmpSize) + "Decoded from hex:" + Integer.toHexString(tmpSize));
359
 
        }
360
 
        return tmpSize;
361
 
    }
362
 
 
363
 
 
364
 
    /**
365
 
     * Write Frame raw data
366
 
     *
367
 
     * @throws IOException
368
 
     */
369
 
    public void write(ByteArrayOutputStream tagBuffer)
370
 
    {
371
 
        logger.info("Write Frame to Buffer" + getIdentifier());
372
 
        //This is where we will write header, move position to where we can
373
 
        //write body
374
 
        ByteBuffer headerBuffer = ByteBuffer.allocate(FRAME_HEADER_SIZE);
375
 
 
376
 
        //Write Frame Body Data
377
 
        ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream();
378
 
        ((AbstractID3v2FrameBody) frameBody).write(bodyOutputStream);
379
 
 
380
 
        //Write Frame Header
381
 
        //Write Frame ID must adjust can only be 3 bytes long
382
 
        headerBuffer.put(Utils.getDefaultBytes(getIdentifier(), "ISO-8859-1"), 0, FRAME_ID_SIZE);
383
 
        encodeSize(headerBuffer, frameBody.getSize());
384
 
 
385
 
        //Add header to the Byte Array Output Stream
386
 
        try
387
 
        {
388
 
            tagBuffer.write(headerBuffer.array());
389
 
 
390
 
            //Add body to the Byte Array Output Stream
391
 
            tagBuffer.write(bodyOutputStream.toByteArray());
392
 
        }
393
 
        catch (IOException ioe)
394
 
        {
395
 
            //This could never happen coz not writing to file, so convert to RuntimeException
396
 
            throw new RuntimeException(ioe);
397
 
        }
398
 
    }
399
 
 
400
 
    /**
401
 
     * Write Frame Size (can now be accurately calculated, have to convert 4 byte int
402
 
     * to 3 byte format.
403
 
     * @param headerBuffer
404
 
     * @param size
405
 
     */
406
 
    private void encodeSize(ByteBuffer headerBuffer, int size)
407
 
    {
408
 
        headerBuffer.put((byte) ((size & 0x00FF0000) >> 16));
409
 
        headerBuffer.put((byte) ((size & 0x0000FF00) >> 8));
410
 
        headerBuffer.put((byte) (size & 0x000000FF));
411
 
        logger.fine("Frame Size Is Actual:" + size + ":Encoded bin:" + Integer.toBinaryString(size) + ":Encoded Hex" + Integer.toHexString(size));
412
 
    }
413
 
 
414
 
    /**
415
 
     * Does the frame identifier meet the syntax for a idv3v2 frame identifier.
416
 
     * must start with a capital letter and only contain capital letters and numbers
417
 
     *
418
 
     * @param identifier
419
 
     * @return
420
 
     */
421
 
    public boolean isValidID3v2FrameIdentifier(String identifier)
422
 
    {
423
 
        Matcher m = ID3v22Frame.validFrameIdentifier.matcher(identifier);
424
 
        return m.matches();
425
 
    }
426
 
 
427
 
    /**
428
 
     * Return String Representation of body
429
 
     */
430
 
    public void createStructure()
431
 
    {
432
 
        MP3File.getStructureFormatter().openHeadingElement(TYPE_FRAME, getIdentifier());
433
 
        MP3File.getStructureFormatter().addElement(TYPE_FRAME_SIZE, frameSize);
434
 
        frameBody.createStructure();
435
 
        MP3File.getStructureFormatter().closeHeadingElement(TYPE_FRAME);
436
 
    }
437
 
 
438
 
    /**
439
 
     * @return true if considered a common frame
440
 
     */
441
 
    public boolean isCommon()
442
 
    {
443
 
        return ID3v22Frames.getInstanceOf().isCommon(getId());
444
 
    }
445
 
 
446
 
    /**
447
 
     * @return true if considered a common frame
448
 
     */
449
 
    public boolean isBinary()
450
 
    {
451
 
        return ID3v22Frames.getInstanceOf().isBinary(getId());
452
 
    }
453
 
}
 
1
/*
 
2
 *  MusicTag Copyright (C)2003,2004
 
3
 *
 
4
 *  This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
 
5
 *  General Public  License as published by the Free Software Foundation; either version 2.1 of the License,
 
6
 *  or (at your option) any later version.
 
7
 *
 
8
 *  This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 
9
 *  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
10
 *  See the GNU Lesser General Public License for more details.
 
11
 *
 
12
 *  You should have received a copy of the GNU Lesser General Public License along with this library; if not,
 
13
 *  you can get a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software
 
14
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 
15
 */
 
16
package org.jaudiotagger.tag.id3;
 
17
 
 
18
import org.jaudiotagger.audio.generic.Utils;
 
19
import org.jaudiotagger.audio.mp3.MP3File;
 
20
import org.jaudiotagger.tag.*;
 
21
import org.jaudiotagger.tag.id3.framebody.AbstractID3v2FrameBody;
 
22
import org.jaudiotagger.tag.id3.framebody.FrameBodyDeprecated;
 
23
import org.jaudiotagger.tag.id3.framebody.FrameBodyUnsupported;
 
24
import org.jaudiotagger.tag.id3.valuepair.TextEncoding;
 
25
import org.jaudiotagger.utils.EqualsUtil;
 
26
 
 
27
import java.io.ByteArrayOutputStream;
 
28
import java.io.IOException;
 
29
import java.math.BigInteger;
 
30
import java.nio.ByteBuffer;
 
31
import java.util.logging.Level;
 
32
import java.util.regex.Matcher;
 
33
import java.util.regex.Pattern;
 
34
 
 
35
/**
 
36
 * Represents an ID3v2.2 frame.
 
37
 *
 
38
 * @author : Paul Taylor
 
39
 * @author : Eric Farng
 
40
 * @version $Id: ID3v22Frame.java 929 2010-11-17 12:36:46Z paultaylor $
 
41
 */
 
42
public class ID3v22Frame extends AbstractID3v2Frame
 
43
{
 
44
    private static Pattern validFrameIdentifier = Pattern.compile("[A-Z][0-9A-Z]{2}");
 
45
 
 
46
    protected static final int FRAME_ID_SIZE = 3;
 
47
    protected static final int FRAME_SIZE_SIZE = 3;
 
48
    protected static final int FRAME_HEADER_SIZE = FRAME_ID_SIZE + FRAME_SIZE_SIZE;
 
49
 
 
50
    public ID3v22Frame()
 
51
    {
 
52
 
 
53
    }
 
54
 
 
55
    protected int getFrameIdSize()
 
56
    {
 
57
        return FRAME_ID_SIZE;
 
58
    }
 
59
 
 
60
    protected int getFrameSizeSize()
 
61
    {
 
62
        return FRAME_SIZE_SIZE;
 
63
    }
 
64
 
 
65
    protected int getFrameHeaderSize()
 
66
    {
 
67
        return FRAME_HEADER_SIZE;
 
68
    }
 
69
 
 
70
    /**
 
71
     * Creates a new ID3v22 Frame with given body
 
72
     *
 
73
     * @param body New body and frame is based on this
 
74
     */
 
75
    public ID3v22Frame(AbstractID3v2FrameBody body)
 
76
    {
 
77
        super(body);
 
78
    }
 
79
 
 
80
     /**
 
81
     * Compare for equality
 
82
     * To be deemed equal obj must be a IDv23Frame with the same identifier
 
83
     * and the same flags.
 
84
     * containing the same body,datatype list ectera.
 
85
     * equals() method is made up from all the various components
 
86
     *
 
87
     * @param obj
 
88
     * @return if true if this object is equivalent to obj
 
89
     */
 
90
    public boolean equals(Object obj)
 
91
    {
 
92
        if ( this == obj ) return true;
 
93
 
 
94
        if (!(obj instanceof ID3v22Frame))
 
95
        {
 
96
            return false;
 
97
        }
 
98
        ID3v22Frame that = (ID3v22Frame) obj;
 
99
 
 
100
 
 
101
        return
 
102
              EqualsUtil.areEqual(this.statusFlags, that.statusFlags) &&
 
103
              EqualsUtil.areEqual(this.encodingFlags, that.encodingFlags) &&
 
104
              super.equals(that);
 
105
 
 
106
    }
 
107
 
 
108
    /**
 
109
     * Creates a new ID3v22 Frame of type identifier.
 
110
     * <p/>
 
111
     * An empty body of the correct type will be automatically created. This constructor should be used when wish to
 
112
     * create a new frame from scratch using user values
 
113
     * @param identifier
 
114
     */
 
115
    @SuppressWarnings("unchecked")
 
116
    public ID3v22Frame(String identifier)
 
117
    {
 
118
 
 
119
        logger.info("Creating empty frame of type" + identifier);
 
120
        String bodyIdentifier = identifier;
 
121
        this.identifier = identifier;
 
122
 
 
123
        //If dealing with v22 identifier (Note this constructor is used by all three tag versions)
 
124
        if (ID3Tags.isID3v22FrameIdentifier(bodyIdentifier))
 
125
        {
 
126
            //Does it have its own framebody (PIC,CRM) or are we using v23/v24 body (the normal case)
 
127
            if (ID3Tags.forceFrameID22To23(bodyIdentifier) != null)
 
128
            {
 
129
                //Do not convert
 
130
            }
 
131
            else if(bodyIdentifier.equals("CRM"))
 
132
            {
 
133
                //Do not convert.
 
134
                //TODO we don't have a way of converting this to v23 which is why its not in the ForceMap
 
135
            }
 
136
            //TODO Improve messy fix for datetime
 
137
            //TODO need to check in case v22 body does exist before using V23 body(e.g PIC)
 
138
            else if ((bodyIdentifier.equals(ID3v22Frames.FRAME_ID_V2_TYER)) || (bodyIdentifier.equals(ID3v22Frames.FRAME_ID_V2_TIME)))
 
139
            {
 
140
                bodyIdentifier = ID3v24Frames.FRAME_ID_YEAR;
 
141
            }
 
142
            // Have to check for v22 because most don't have own body they use v23 or v24
 
143
            // body to hold the data, the frame is identified by its identifier, the body identifier
 
144
            // is just to create a body suitable for writing the data to
 
145
            else if (ID3Tags.isID3v22FrameIdentifier(bodyIdentifier))
 
146
            {
 
147
                bodyIdentifier = ID3Tags.convertFrameID22To23(bodyIdentifier);
 
148
            }
 
149
        }
 
150
 
 
151
        // Use reflection to map id to frame body, which makes things much easier
 
152
        // to keep things up to date.
 
153
        try
 
154
        {
 
155
            Class<AbstractID3v2FrameBody> c = (Class<AbstractID3v2FrameBody>) Class.forName("org.jaudiotagger.tag.id3.framebody.FrameBody" + bodyIdentifier);
 
156
            frameBody = c.newInstance();
 
157
        }
 
158
        catch (ClassNotFoundException cnfe)
 
159
        {
 
160
            logger.log(Level.SEVERE, cnfe.getMessage(), cnfe);
 
161
            frameBody = new FrameBodyUnsupported(identifier);
 
162
        }
 
163
        //Instantiate Interface/Abstract should not happen
 
164
        catch (InstantiationException ie)
 
165
        {
 
166
            logger.log(Level.SEVERE, ie.getMessage(), ie);
 
167
            throw new RuntimeException(ie);
 
168
        }
 
169
        //Private Constructor shouild not happen
 
170
        catch (IllegalAccessException iae)
 
171
        {
 
172
            logger.log(Level.SEVERE, iae.getMessage(), iae);
 
173
            throw new RuntimeException(iae);
 
174
        }
 
175
        frameBody.setHeader(this);
 
176
        logger.info("Created empty frame of type" + this.identifier + "with frame body of" + bodyIdentifier);
 
177
 
 
178
    }
 
179
 
 
180
    /**
 
181
     * Copy Constructor
 
182
     * <p/>
 
183
     * Creates a new v22 frame based on another v22 frame
 
184
     * @param frame
 
185
     */
 
186
    public ID3v22Frame(ID3v22Frame frame)
 
187
    {
 
188
        super(frame);
 
189
        logger.info("Creating frame from a frame of same version");
 
190
    }
 
191
 
 
192
    private void createV22FrameFromV23Frame(ID3v23Frame frame) throws InvalidFrameException
 
193
    {
 
194
        identifier = ID3Tags.convertFrameID23To22(frame.getIdentifier());
 
195
        if (identifier != null)
 
196
        {
 
197
            logger.info("V2:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 
198
            this.frameBody = (AbstractID3v2FrameBody) ID3Tags.copyObject(frame.getBody());
 
199
        }
 
200
        // Is it a known v3 frame which needs forcing to v2 frame e.g. APIC - PIC
 
201
        else if (ID3Tags.isID3v23FrameIdentifier(frame.getIdentifier()))
 
202
        {
 
203
            identifier = ID3Tags.forceFrameID23To22(frame.getIdentifier());
 
204
            if (identifier != null)
 
205
            {
 
206
                logger.info("V2:Force:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 
207
                this.frameBody = this.readBody(identifier, (AbstractID3v2FrameBody) frame.getBody());
 
208
            }
 
209
            // No mechanism exists to convert it to a v22 frame
 
210
            else
 
211
            {
 
212
                throw new InvalidFrameException("Unable to convert v23 frame:" + frame.getIdentifier() + " to a v22 frame");
 
213
            }
 
214
        }
 
215
        //Deprecated frame for v23
 
216
        else if (frame.getBody() instanceof FrameBodyDeprecated)
 
217
        {
 
218
            //Was it valid for this tag version, if so try and reconstruct
 
219
            if (ID3Tags.isID3v22FrameIdentifier(frame.getIdentifier()))
 
220
            {
 
221
                this.frameBody = frame.getBody();
 
222
                identifier = frame.getIdentifier();
 
223
                logger.info("DEPRECATED:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 
224
            }
 
225
            //or was it still deprecated, if so leave as is
 
226
            else
 
227
            {
 
228
                this.frameBody = new FrameBodyDeprecated((FrameBodyDeprecated) frame.getBody());
 
229
                identifier = frame.getIdentifier();
 
230
                logger.info("DEPRECATED:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 
231
            }
 
232
        }
 
233
        // Unknown Frame e.g NCON
 
234
        else
 
235
        {
 
236
            this.frameBody = new FrameBodyUnsupported((FrameBodyUnsupported) frame.getBody());
 
237
            identifier = frame.getIdentifier();
 
238
            logger.info("v2:UNKNOWN:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 
239
        }
 
240
    }
 
241
 
 
242
    /**
 
243
     * Creates a new ID3v22 Frame from another frame of a different tag version
 
244
     *
 
245
     * @param frame to construct the new frame from
 
246
     * @throws org.jaudiotagger.tag.InvalidFrameException
 
247
     */
 
248
    public ID3v22Frame(AbstractID3v2Frame frame) throws InvalidFrameException
 
249
    {
 
250
        logger.info("Creating frame from a frame of a different version");
 
251
        if (frame instanceof ID3v22Frame)
 
252
        {
 
253
            throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument");
 
254
        }
 
255
 
 
256
        // If it is a v24 frame is it possible to convert it into a v23 frame, anmd then convert from that
 
257
        if (frame instanceof ID3v24Frame)
 
258
        {
 
259
            ID3v23Frame v23Frame = new ID3v23Frame(frame);
 
260
            createV22FrameFromV23Frame(v23Frame);
 
261
        }
 
262
        //If it is a v23 frame is it possible to convert it into a v22 frame
 
263
        else if (frame instanceof ID3v23Frame)
 
264
        {
 
265
            createV22FrameFromV23Frame((ID3v23Frame) frame);
 
266
        }
 
267
        this.frameBody.setHeader(this);
 
268
        logger.info("Created frame from a frame of a different version");
 
269
    }
 
270
 
 
271
    /**
 
272
     * Creates a new ID3v22Frame datatype by reading from byteBuffer.
 
273
     *
 
274
     * @param byteBuffer to read from
 
275
     * @param loggingFilename
 
276
     * @throws org.jaudiotagger.tag.InvalidFrameException
 
277
     */
 
278
    public ID3v22Frame(ByteBuffer byteBuffer, String loggingFilename) throws InvalidFrameException, InvalidDataTypeException
 
279
    {
 
280
        setLoggingFilename(loggingFilename);
 
281
        read(byteBuffer);
 
282
    }
 
283
 
 
284
    /**
 
285
     * Creates a new ID3v23Frame datatype by reading from byteBuffer.
 
286
     *
 
287
     * @param byteBuffer to read from
 
288
     * @deprecated use {@link #ID3v22Frame(ByteBuffer,String)} instead
 
289
     * @throws org.jaudiotagger.tag.InvalidFrameException
 
290
     */
 
291
    public ID3v22Frame(ByteBuffer byteBuffer) throws InvalidFrameException, InvalidDataTypeException
 
292
    {
 
293
        this(byteBuffer, "");
 
294
    }
 
295
 
 
296
    /**
 
297
     * Return size of frame
 
298
     *
 
299
     * @return int size of frame
 
300
     */
 
301
    public int getSize()
 
302
    {
 
303
        return frameBody.getSize() + getFrameHeaderSize();
 
304
    }
 
305
 
 
306
    @Override 
 
307
    protected boolean isPadding(byte[] buffer)
 
308
    {
 
309
        if(
 
310
                (buffer[0]=='\0')&&
 
311
                (buffer[1]=='\0')&&
 
312
                (buffer[2]=='\0')
 
313
           )
 
314
        {
 
315
            return true;
 
316
        }
 
317
        return false;
 
318
    }
 
319
 
 
320
    /**
 
321
     * Read frame from file.
 
322
     * Read the frame header then delegate reading of data to frame body.
 
323
     *
 
324
     * @param byteBuffer
 
325
     */
 
326
    public void read(ByteBuffer byteBuffer) throws InvalidFrameException, InvalidDataTypeException
 
327
    {
 
328
        String identifier = readIdentifier(byteBuffer);
 
329
 
 
330
        byte[] buffer = new byte[getFrameSizeSize()];
 
331
 
 
332
        // Is this a valid identifier?
 
333
        if (!isValidID3v2FrameIdentifier(identifier))
 
334
        {
 
335
            logger.info("Invalid identifier:" + identifier);
 
336
            byteBuffer.position(byteBuffer.position() - (getFrameIdSize() - 1));
 
337
            throw new InvalidFrameIdentifierException(getLoggingFilename() + ":" + identifier + ":is not a valid ID3v2.20 frame");
 
338
        }
 
339
        //Read Frame Size (same size as Frame Id so reuse buffer)
 
340
        byteBuffer.get(buffer, 0, getFrameSizeSize());
 
341
        frameSize = decodeSize(buffer);
 
342
        if (frameSize < 0)
 
343
        {
 
344
            throw new InvalidFrameException(identifier + " has invalid size of:" + frameSize);
 
345
        }
 
346
        else if (frameSize == 0)
 
347
        {
 
348
            //We dont process this frame or add to framemap becuase contains no useful information
 
349
            logger.warning("Empty Frame:" + identifier);
 
350
            throw new EmptyFrameException(identifier + " is empty frame");
 
351
        }
 
352
        else if (frameSize > byteBuffer.remaining())
 
353
        {
 
354
            logger.warning("Invalid Frame size larger than size before mp3 audio:" + identifier);
 
355
            throw new InvalidFrameException(identifier + " is invalid frame");
 
356
        }
 
357
        else
 
358
        {
 
359
            logger.fine("Frame Size Is:" + frameSize);
 
360
            //Convert v2.2 to v2.4 id just for reading the data
 
361
            String id = ID3Tags.convertFrameID22To24(identifier);
 
362
            if (id == null)
 
363
            {
 
364
                //OK,it may be convertable to a v.3 id even though not valid v.4
 
365
                id = ID3Tags.convertFrameID22To23(identifier);
 
366
                if (id == null)
 
367
                {
 
368
                    // Is it a valid v22 identifier so should be able to find a
 
369
                    // frame body for it.
 
370
                    if (ID3Tags.isID3v22FrameIdentifier(identifier))
 
371
                    {
 
372
                        id = identifier;
 
373
                    }
 
374
                    // Unknown so will be created as FrameBodyUnsupported
 
375
                    else
 
376
                    {
 
377
                        id = UNSUPPORTED_ID;
 
378
                    }
 
379
                }
 
380
            }
 
381
            logger.fine("Identifier was:" + identifier + " reading using:" + id);
 
382
 
 
383
            //Create Buffer that only contains the body of this frame rather than the remainder of tag
 
384
            ByteBuffer frameBodyBuffer = byteBuffer.slice();
 
385
            frameBodyBuffer.limit(frameSize);
 
386
 
 
387
            try
 
388
            {
 
389
                frameBody = readBody(id, frameBodyBuffer, frameSize);
 
390
            }
 
391
            finally
 
392
            {
 
393
                //Update position of main buffer, so no attempt is made to reread these bytes
 
394
                byteBuffer.position(byteBuffer.position() + frameSize);
 
395
            }
 
396
        }
 
397
    }
 
398
 
 
399
    /**
 
400
     * Read Frame Size, which has to be decoded
 
401
     * @param buffer
 
402
     * @return
 
403
     */
 
404
    private int decodeSize(byte[] buffer)
 
405
    {
 
406
        BigInteger bi = new BigInteger(buffer);
 
407
        int tmpSize = bi.intValue();
 
408
        if (tmpSize < 0)
 
409
        {
 
410
            logger.warning("Invalid Frame Size of:" + tmpSize + "Decoded from bin:" + Integer.toBinaryString(tmpSize) + "Decoded from hex:" + Integer.toHexString(tmpSize));
 
411
        }
 
412
        return tmpSize;
 
413
    }
 
414
 
 
415
 
 
416
    /**
 
417
     * Write Frame raw data
 
418
     *
 
419
     * @throws IOException
 
420
     */
 
421
    public void write(ByteArrayOutputStream tagBuffer)
 
422
    {
 
423
        logger.info("Write Frame to Buffer" + getIdentifier());
 
424
        //This is where we will write header, move position to where we can
 
425
        //write body
 
426
        ByteBuffer headerBuffer = ByteBuffer.allocate(getFrameHeaderSize());
 
427
 
 
428
        //Write Frame Body Data
 
429
        ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream();
 
430
        ((AbstractID3v2FrameBody) frameBody).write(bodyOutputStream);
 
431
 
 
432
        //Write Frame Header
 
433
        //Write Frame ID must adjust can only be 3 bytes long
 
434
        headerBuffer.put(Utils.getDefaultBytes(getIdentifier(), "ISO-8859-1"), 0, getFrameIdSize());
 
435
        encodeSize(headerBuffer, frameBody.getSize());
 
436
 
 
437
        //Add header to the Byte Array Output Stream
 
438
        try
 
439
        {
 
440
            tagBuffer.write(headerBuffer.array());
 
441
 
 
442
            //Add body to the Byte Array Output Stream
 
443
            tagBuffer.write(bodyOutputStream.toByteArray());
 
444
        }
 
445
        catch (IOException ioe)
 
446
        {
 
447
            //This could never happen coz not writing to file, so convert to RuntimeException
 
448
            throw new RuntimeException(ioe);
 
449
        }
 
450
    }
 
451
 
 
452
    /**
 
453
     * Write Frame Size (can now be accurately calculated, have to convert 4 byte int
 
454
     * to 3 byte format.
 
455
     * @param headerBuffer
 
456
     * @param size
 
457
     */
 
458
    private void encodeSize(ByteBuffer headerBuffer, int size)
 
459
    {
 
460
        headerBuffer.put((byte) ((size & 0x00FF0000) >> 16));
 
461
        headerBuffer.put((byte) ((size & 0x0000FF00) >> 8));
 
462
        headerBuffer.put((byte) (size & 0x000000FF));
 
463
        logger.fine("Frame Size Is Actual:" + size + ":Encoded bin:" + Integer.toBinaryString(size) + ":Encoded Hex" + Integer.toHexString(size));
 
464
    }
 
465
 
 
466
    /**
 
467
     * Does the frame identifier meet the syntax for a idv3v2 frame identifier.
 
468
     * must start with a capital letter and only contain capital letters and numbers
 
469
     *
 
470
     * @param identifier
 
471
     * @return
 
472
     */
 
473
    public boolean isValidID3v2FrameIdentifier(String identifier)
 
474
    {
 
475
        Matcher m = ID3v22Frame.validFrameIdentifier.matcher(identifier);
 
476
        return m.matches();
 
477
    }
 
478
 
 
479
    /**
 
480
     * Return String Representation of body
 
481
     */
 
482
    public void createStructure()
 
483
    {
 
484
        MP3File.getStructureFormatter().openHeadingElement(TYPE_FRAME, getIdentifier());
 
485
        MP3File.getStructureFormatter().addElement(TYPE_FRAME_SIZE, frameSize);
 
486
        frameBody.createStructure();
 
487
        MP3File.getStructureFormatter().closeHeadingElement(TYPE_FRAME);
 
488
    }
 
489
 
 
490
    /**
 
491
     * @return true if considered a common frame
 
492
     */
 
493
    public boolean isCommon()
 
494
    {
 
495
        return ID3v22Frames.getInstanceOf().isCommon(getId());
 
496
    }
 
497
 
 
498
    /**
 
499
     * @return true if considered a common frame
 
500
     */
 
501
    public boolean isBinary()
 
502
    {
 
503
        return ID3v22Frames.getInstanceOf().isBinary(getId());
 
504
    }
 
505
 
 
506
     /**
 
507
     * Sets the charset encoding used by the field.
 
508
     *
 
509
     * @param encoding charset.
 
510
     */
 
511
    public void setEncoding(String encoding)
 
512
    {
 
513
        Integer encodingId = TextEncoding.getInstanceOf().getIdForValue(encoding);
 
514
        if(encoding!=null)
 
515
        {
 
516
            if(encodingId <2)
 
517
            {
 
518
                this.getBody().setTextEncoding(encodingId.byteValue());
 
519
            }
 
520
        }
 
521
    }
 
522
}