1/*
2 * Copyright 2008 CoreMedia AG, Hamburg
3 *
4 * Licensed under the Apache License, Version 2.0 (the License);
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an AS IS BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.coremedia.iso;
18
19import com.googlecode.mp4parser.AbstractContainerBox;
20import com.coremedia.iso.boxes.Box;
21import com.coremedia.iso.boxes.MovieBox;
22import com.googlecode.mp4parser.annotations.DoNotParseDetail;
23
24import java.io.*;
25import java.nio.ByteBuffer;
26import java.nio.channels.FileChannel;
27import java.nio.channels.ReadableByteChannel;
28import java.nio.channels.WritableByteChannel;
29
30/**
31 * The most upper container for ISO Boxes. It is a container box that is a file.
32 * Uses IsoBufferWrapper  to access the underlying file.
33 */
34@DoNotParseDetail
35public class IsoFile extends AbstractContainerBox implements Closeable {
36    protected BoxParser boxParser = new PropertyBoxParserImpl();
37    ReadableByteChannel byteChannel;
38
39    public IsoFile() {
40        super("");
41    }
42
43    public IsoFile(File f) throws IOException {
44        super("");
45        this.byteChannel = new FileInputStream(f).getChannel();
46        boxParser = createBoxParser();
47        parse();
48    }
49
50    public IsoFile(ReadableByteChannel byteChannel) throws IOException {
51        super("");
52        this.byteChannel = byteChannel;
53        boxParser = createBoxParser();
54        parse();
55    }
56
57    public IsoFile(ReadableByteChannel byteChannel, BoxParser boxParser) throws IOException {
58        super("");
59        this.byteChannel = byteChannel;
60        this.boxParser = boxParser;
61        parse();
62
63
64    }
65
66    protected BoxParser createBoxParser() {
67        return new PropertyBoxParserImpl();
68    }
69
70
71    @Override
72    public void _parseDetails(ByteBuffer content) {
73        // there are no details to parse we should be just file
74    }
75
76    public void parse(ReadableByteChannel inFC, ByteBuffer header, long contentSize, AbstractBoxParser abstractBoxParser) throws IOException {
77        throw new IOException("This method is not meant to be called. Use #parse() directly.");
78    }
79
80    private void parse() throws IOException {
81
82        boolean done = false;
83        while (!done) {
84            try {
85                Box box = boxParser.parseBox(byteChannel, this);
86                if (box != null) {
87                    //  System.err.println(box.getType());
88                    boxes.add(box);
89                } else {
90                    done = true;
91                }
92            } catch (EOFException e) {
93                done = true;
94            }
95        }
96    }
97
98    @DoNotParseDetail
99    public String toString() {
100        StringBuilder buffer = new StringBuilder();
101        buffer.append("IsoFile[");
102        if (boxes == null) {
103            buffer.append("unparsed");
104        } else {
105            for (int i = 0; i < boxes.size(); i++) {
106                if (i > 0) {
107                    buffer.append(";");
108                }
109                buffer.append(boxes.get(i).toString());
110            }
111        }
112        buffer.append("]");
113        return buffer.toString();
114    }
115
116    @DoNotParseDetail
117    public static byte[] fourCCtoBytes(String fourCC) {
118        byte[] result = new byte[4];
119        if (fourCC != null) {
120            for (int i = 0; i < Math.min(4, fourCC.length()); i++) {
121                result[i] = (byte) fourCC.charAt(i);
122            }
123        }
124        return result;
125    }
126
127    @DoNotParseDetail
128    public static String bytesToFourCC(byte[] type) {
129        byte[] result = new byte[]{0, 0, 0, 0};
130        if (type != null) {
131            System.arraycopy(type, 0, result, 0, Math.min(type.length, 4));
132        }
133        try {
134            return new String(result, "ISO-8859-1");
135        } catch (UnsupportedEncodingException e) {
136            throw new Error("Required character encoding is missing", e);
137        }
138    }
139
140
141    @Override
142    public long getNumOfBytesToFirstChild() {
143        return 0;
144    }
145
146    @Override
147    public long getSize() {
148        long size = 0;
149        for (Box box : boxes) {
150            size += box.getSize();
151        }
152        return size;
153    }
154
155    @Override
156    public IsoFile getIsoFile() {
157        return this;
158    }
159
160
161    /**
162     * Shortcut to get the MovieBox since it is often needed and present in
163     * nearly all ISO 14496 files (at least if they are derived from MP4 ).
164     *
165     * @return the MovieBox or <code>null</code>
166     */
167    @DoNotParseDetail
168    public MovieBox getMovieBox() {
169        for (Box box : boxes) {
170            if (box instanceof MovieBox) {
171                return (MovieBox) box;
172            }
173        }
174        return null;
175    }
176
177    public void getBox(WritableByteChannel os) throws IOException {
178        for (Box box : boxes) {
179
180            if (os instanceof FileChannel) {
181                long startPos = ((FileChannel) os).position();
182                box.getBox(os);
183                long size = ((FileChannel) os).position() - startPos;
184                assert size == box.getSize();
185            } else {
186                box.getBox(os);
187            }
188
189        }
190    }
191
192    public void close() throws IOException {
193        this.byteChannel.close();
194    }
195}
196