Home | History | Annotate | Download | only in piff
      1 package com.googlecode.mp4parser.boxes.piff;
      2 
      3 import com.coremedia.iso.IsoFile;
      4 import com.coremedia.iso.IsoTypeReader;
      5 import com.coremedia.iso.IsoTypeWriter;
      6 import com.googlecode.mp4parser.util.Path;
      7 
      8 import java.io.FileInputStream;
      9 import java.io.FileNotFoundException;
     10 import java.io.IOException;
     11 import java.io.UnsupportedEncodingException;
     12 import java.nio.ByteBuffer;
     13 import java.util.ArrayList;
     14 import java.util.Collections;
     15 import java.util.List;
     16 
     17 /**
     18  * Specifications > Microsoft PlayReady Format Specification > 2. PlayReady Media Format > 2.7. ASF GUIDs
     19  * <p/>
     20  * <p/>
     21  * ASF_Protection_System_Identifier_Object
     22  * 9A04F079-9840-4286-AB92E65BE0885F95
     23  * <p/>
     24  * ASF_Content_Protection_System_Microsoft_PlayReady
     25  * F4637010-03C3-42CD-B932B48ADF3A6A54
     26  * <p/>
     27  * ASF_StreamType_PlayReady_Encrypted_Command_Media
     28  * 8683973A-6639-463A-ABD764F1CE3EEAE0
     29  * <p/>
     30  * <p/>
     31  * Specifications > Microsoft PlayReady Format Specification > 2. PlayReady Media Format > 2.5. Data Objects > 2.5.1. Payload Extension for AES in Counter Mode
     32  * <p/>
     33  * The sample Id is used as the IV in CTR mode. Block offset, starting at 0 and incremented by 1 after every 16 bytes, from the beginning of the sample is used as the Counter.
     34  * <p/>
     35  * The sample ID for each sample (media object) is stored as an ASF payload extension system with the ID of ASF_Payload_Extension_Encryption_SampleID = {6698B84E-0AFA-4330-AEB2-1C0A98D7A44D}. The payload extension can be stored as a fixed size extension of 8 bytes.
     36  * <p/>
     37  * The sample ID is always stored in big-endian byte order.
     38  */
     39 public class PlayReadyHeader extends ProtectionSpecificHeader {
     40     private long length;
     41     private List<PlayReadyRecord> records;
     42 
     43     public PlayReadyHeader() {
     44 
     45     }
     46 
     47     @Override
     48     public void parse(ByteBuffer byteBuffer) {
     49         /*
     50    Length DWORD 32
     51 
     52    PlayReady Record Count WORD 16
     53 
     54    PlayReady Records See Text Varies
     55 
     56         */
     57 
     58         length = IsoTypeReader.readUInt32BE(byteBuffer);
     59         int recordCount = IsoTypeReader.readUInt16BE(byteBuffer);
     60 
     61         records = PlayReadyRecord.createFor(byteBuffer, recordCount);
     62     }
     63 
     64     @Override
     65     public ByteBuffer getData() {
     66 
     67         int size = 4 + 2;
     68         for (PlayReadyRecord record : records) {
     69             size += 2 + 2;
     70             size += record.getValue().rewind().limit();
     71         }
     72         ByteBuffer byteBuffer = ByteBuffer.allocate(size);
     73 
     74         IsoTypeWriter.writeUInt32BE(byteBuffer, size);
     75         IsoTypeWriter.writeUInt16BE(byteBuffer, records.size());
     76         for (PlayReadyRecord record : records) {
     77             IsoTypeWriter.writeUInt16BE(byteBuffer, record.type);
     78             IsoTypeWriter.writeUInt16BE(byteBuffer, record.getValue().limit());
     79             ByteBuffer tmp4debug = record.getValue();
     80             byteBuffer.put(tmp4debug);
     81         }
     82 
     83         return byteBuffer;
     84     }
     85 
     86     public void setRecords(List<PlayReadyRecord> records) {
     87         this.records = records;
     88     }
     89 
     90     public List<PlayReadyRecord> getRecords() {
     91         return Collections.unmodifiableList(records);
     92     }
     93 
     94     @Override
     95     public String toString() {
     96         final StringBuilder sb = new StringBuilder();
     97         sb.append("PlayReadyHeader");
     98         sb.append("{length=").append(length);
     99         sb.append(", recordCount=").append(records.size());
    100         sb.append(", records=").append(records);
    101         sb.append('}');
    102         return sb.toString();
    103     }
    104 
    105     public static abstract class PlayReadyRecord {
    106         int type;
    107 
    108 
    109         public PlayReadyRecord(int type) {
    110             this.type = type;
    111         }
    112 
    113         public static List<PlayReadyRecord> createFor(ByteBuffer byteBuffer, int recordCount) {
    114             List<PlayReadyRecord> records = new ArrayList<PlayReadyRecord>(recordCount);
    115 
    116             for (int i = 0; i < recordCount; i++) {
    117                 PlayReadyRecord record;
    118                 int type = IsoTypeReader.readUInt16BE(byteBuffer);
    119                 int length = IsoTypeReader.readUInt16BE(byteBuffer);
    120                 switch (type) {
    121                     case 0x1:
    122                         record = new RMHeader();
    123                         break;
    124                     case 0x2:
    125                         record = new DefaulPlayReadyRecord(0x02);
    126                         break;
    127                     case 0x3:
    128                         record = new EmeddedLicenseStore();
    129                         break;
    130                     default:
    131                         record = new DefaulPlayReadyRecord(type);
    132                 }
    133                 record.parse((ByteBuffer) byteBuffer.slice().limit(length));
    134                 byteBuffer.position(byteBuffer.position() + length);
    135                 records.add(record);
    136             }
    137 
    138             return records;
    139         }
    140 
    141         public abstract void parse(ByteBuffer bytes);
    142 
    143         @Override
    144         public String toString() {
    145             final StringBuilder sb = new StringBuilder();
    146             sb.append("PlayReadyRecord");
    147             sb.append("{type=").append(type);
    148             sb.append(", length=").append(getValue().limit());
    149 //            sb.append(", value=").append(Hex.encodeHex(getValue())).append('\'');
    150             sb.append('}');
    151             return sb.toString();
    152         }
    153 
    154         public abstract ByteBuffer getValue();
    155 
    156         public static class RMHeader extends PlayReadyRecord {
    157             String header;
    158 
    159             public RMHeader() {
    160                 super(0x01);
    161             }
    162 
    163             @Override
    164             public void parse(ByteBuffer bytes) {
    165                 try {
    166                     byte[] str = new byte[bytes.slice().limit()];
    167                     bytes.get(str);
    168                     header = new String(str, "UTF-16LE");
    169                 } catch (UnsupportedEncodingException e) {
    170                     throw new RuntimeException(e);
    171                 }
    172             }
    173 
    174             @Override
    175             public ByteBuffer getValue() {
    176                 byte[] headerBytes;
    177                 try {
    178                     headerBytes = header.getBytes("UTF-16LE");
    179                 } catch (UnsupportedEncodingException e) {
    180                     throw new RuntimeException(e);
    181                 }
    182                 return ByteBuffer.wrap(headerBytes);
    183             }
    184 
    185             public void setHeader(String header) {
    186                 this.header = header;
    187             }
    188 
    189             public String getHeader() {
    190                 return header;
    191             }
    192 
    193             @Override
    194             public String toString() {
    195                 final StringBuilder sb = new StringBuilder();
    196                 sb.append("RMHeader");
    197                 sb.append("{length=").append(getValue().limit());
    198                 sb.append(", header='").append(header).append('\'');
    199                 sb.append('}');
    200                 return sb.toString();
    201             }
    202         }
    203 
    204         public static class EmeddedLicenseStore extends PlayReadyRecord {
    205             ByteBuffer value;
    206 
    207             public EmeddedLicenseStore() {
    208                 super(0x03);
    209             }
    210 
    211             @Override
    212             public void parse(ByteBuffer bytes) {
    213                 this.value = bytes.duplicate();
    214             }
    215 
    216             @Override
    217             public ByteBuffer getValue() {
    218                 return value;
    219             }
    220 
    221             @Override
    222             public String toString() {
    223                 final StringBuilder sb = new StringBuilder();
    224                 sb.append("EmeddedLicenseStore");
    225                 sb.append("{length=").append(getValue().limit());
    226                 //sb.append(", value='").append(Hex.encodeHex(getValue())).append('\'');
    227                 sb.append('}');
    228                 return sb.toString();
    229             }
    230         }
    231 
    232         public static class DefaulPlayReadyRecord extends PlayReadyRecord {
    233             ByteBuffer value;
    234 
    235             public DefaulPlayReadyRecord(int type) {
    236                 super(type);
    237             }
    238 
    239             @Override
    240             public void parse(ByteBuffer bytes) {
    241                 this.value = bytes.duplicate();
    242             }
    243 
    244             @Override
    245             public ByteBuffer getValue() {
    246                 return value;
    247             }
    248 
    249         }
    250 
    251     }
    252 
    253 }
    254