001package net.filebot.mediainfo;
002
003import java.io.File;
004import java.io.IOException;
005import java.nio.ByteBuffer;
006import java.nio.channels.FileChannel;
007import java.nio.file.StandardOpenOption;
008import java.util.LinkedHashMap;
009import java.util.Map;
010
011import com.sun.jna.Memory;
012import com.sun.jna.Platform;
013import com.sun.jna.Pointer;
014import com.sun.jna.WString;
015
016public class MediaInfo implements MediaInfoProperties {
017
018        private final Pointer handle;
019
020        public MediaInfo() {
021                try {
022                        // initialize native handle
023                        handle = MediaInfoLibrary.INSTANCE.New();
024
025                        // initialize default options
026                        option("Language", "raw");
027                        option("Complete", "1");
028                } catch (LinkageError e) {
029                        throw new MediaInfoException(e);
030                }
031        }
032
033        public synchronized int open(File file) {
034                return MediaInfoLibrary.INSTANCE.Open(handle, new WString(file.getPath()));
035        }
036
037        public synchronized long read(File file, int chunk) throws IOException {
038                try (FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.READ)) {
039                        long size = channel.size();
040
041                        // NOTE: We must not use Memory.close() since it was only added recently and is thus not yet commonly available on Linux platforms
042                        Memory memory = new Memory(chunk);
043                        ByteBuffer buffer = memory.getByteBuffer(0, memory.size());
044
045                        // record total number of bytes read
046                        long total = 0;
047
048                        MediaInfoLibrary.INSTANCE.Open_Buffer_Init(handle, size, 0);
049                        int read = -1;
050
051                        // read file chunk by chunk
052                        while ((read = channel.read(buffer)) >= 0) {
053                                total += read;
054
055                                if ((MediaInfoLibrary.INSTANCE.Open_Buffer_Continue(handle, memory, read) & 0x08) == 0x08) {
056                                        break;
057                                }
058
059                                long seek = MediaInfoLibrary.INSTANCE.Open_Buffer_Continue_GoTo_Get(handle);
060                                if (seek >= 0) {
061                                        channel.position(seek);
062                                        MediaInfoLibrary.INSTANCE.Open_Buffer_Init(handle, size, seek);
063                                }
064
065                                buffer.rewind();
066                        }
067
068                        MediaInfoLibrary.INSTANCE.Open_Buffer_Finalize(handle);
069                        return total;
070                }
071        }
072
073        public synchronized String option(String option) {
074                return option(option, "");
075        }
076
077        public synchronized String option(String option, String value) {
078                return MediaInfoLibrary.INSTANCE.Option(handle, new WString(option), new WString(value)).toString();
079        }
080
081        @Override
082        public synchronized String get(StreamKind streamKind, int streamNumber, String parameter) {
083                return MediaInfoLibrary.INSTANCE.Get(handle, streamKind.ordinal(), streamNumber, new WString(parameter), InfoKind.Text.ordinal(), InfoKind.Name.ordinal()).toString();
084        }
085
086        public synchronized String get(StreamKind streamKind, int streamNumber, int parameterIndex, InfoKind infoKind) {
087                return MediaInfoLibrary.INSTANCE.GetI(handle, streamKind.ordinal(), streamNumber, parameterIndex, infoKind.ordinal()).toString();
088        }
089
090        @Override
091        public synchronized int streamCount(StreamKind streamKind) {
092                // HACK: Count_Get() in MediaInfo.dll does not work correctly on Windows with newer versions of JNA for unknown reasons (see https://github.com/MediaArea/MediaInfo/issues/521)
093                if (Platform.isWindows()) {
094                        String count = get(streamKind, 0, "StreamCount");
095                        return count.isEmpty() ? 0 : Integer.parseInt(count);
096                }
097                return MediaInfoLibrary.INSTANCE.Count_Get(handle, streamKind.ordinal(), -1);
098        }
099
100        public synchronized int parameterCount(StreamKind streamKind, int streamNumber) {
101                return MediaInfoLibrary.INSTANCE.Count_Get(handle, streamKind.ordinal(), streamNumber);
102        }
103
104        @Override
105        public synchronized Map<String, String> map(StreamKind streamKind, int streamNumber) {
106                int parameterCount = parameterCount(streamKind, streamNumber);
107                Map<String, String> streamInfo = new LinkedHashMap<String, String>(parameterCount);
108
109                for (int i = 0; i < parameterCount; i++) {
110                        String value = get(streamKind, streamNumber, i, InfoKind.Text);
111                        if (value.isEmpty()) {
112                                continue;
113                        }
114
115                        String name = get(streamKind, streamNumber, i, InfoKind.Name);
116                        streamInfo.put(name, value);
117                }
118
119                return streamInfo;
120        }
121
122        public synchronized String raw() throws IOException {
123                return MediaInfoLibrary.INSTANCE.Inform(handle).toString();
124        }
125
126        @Override
127        public synchronized void close() {
128                MediaInfoLibrary.INSTANCE.Close(handle);
129                MediaInfoLibrary.INSTANCE.Delete(handle);
130        }
131
132        public enum InfoKind {
133                /**
134                 * Unique name of parameter.
135                 */
136                Name,
137
138                /**
139                 * Value of parameter.
140                 */
141                Text,
142
143                /**
144                 * Unique name of measure unit of parameter.
145                 */
146                Measure,
147
148                /**
149                 * See infooptions_t.
150                 */
151                Options,
152
153                /**
154                 * Translated name of parameter.
155                 */
156                Name_Text,
157
158                /**
159                 * Translated name of measure unit.
160                 */
161                Measure_Text,
162
163                /**
164                 * More information about the parameter.
165                 */
166                Info,
167
168                /**
169                 * How this parameter is supported, could be N (No), B (Beta), R (Read only), W (Read/Write).
170                 */
171                HowTo,
172
173                /**
174                 * Domain of this piece of information.
175                 */
176                Domain;
177        }
178
179        public static String version() {
180                return staticOption("Info_Version");
181        }
182
183        public static String parameters() {
184                return staticOption("Info_Parameters");
185        }
186
187        public static String codecs() {
188                return staticOption("Info_Codecs");
189        }
190
191        public static String capacities() {
192                return staticOption("Info_Capacities");
193        }
194
195        public static String staticOption(String option) {
196                return staticOption(option, "");
197        }
198
199        public static String staticOption(String option, String value) {
200                return MediaInfoLibrary.INSTANCE.Option(null, new WString(option), new WString(value)).toString();
201        }
202
203}