001package videolan.libdvdnav;
002
003import java.io.IOException;
004import java.io.UnsupportedEncodingException;
005import java.util.Objects;
006import lombok.Data;
007import lombok.Synchronized;
008import lombok.ToString;
009import org.fusesource.hawtjni.runtime.JniArg;
010import org.fusesource.hawtjni.runtime.JniClass;
011import org.fusesource.hawtjni.runtime.JniField;
012import org.fusesource.hawtjni.runtime.JniMethod;
013import org.fusesource.hawtjni.runtime.Library;
014
015import static java.nio.charset.StandardCharsets.UTF_8;
016import static org.fusesource.hawtjni.runtime.ArgFlag.CRITICAL;
017import static org.fusesource.hawtjni.runtime.ArgFlag.NO_IN;
018import static org.fusesource.hawtjni.runtime.FieldFlag.CONSTANT;
019import static org.fusesource.hawtjni.runtime.MethodFlag.CONSTANT_INITIALIZER;
020
021/**
022 * {@link DVDNav} is the Java entry-point to
023 * {@link.uri https://www.videolan.org/developers/libdvdnav.html libdvdnav}.
024 *
025 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
026 * @version $Revision: 5985 $
027 */
028@JniClass
029@ToString
030public class DVDNav implements AutoCloseable {
031    private static final Library LIBRARY =
032        new Library("videolan-libdvdnav", DVDNav.class);
033
034    @JniMethod(flags = { CONSTANT_INITIALIZER })
035    private static final native void init();
036
037    static { LIBRARY.load(); init(); }
038
039    /**
040     * {@code <dvdnav/dvdnav.h>}
041     */
042    @JniField(flags = { CONSTANT })
043    private static int
044        DVDNAV_STATUS_ERR,
045        DVDNAV_STATUS_OK;
046
047    /**
048     * {@code <dvdnav/dvdnav_events.h>}
049     */
050    @JniField(flags = { CONSTANT })
051    private static int
052        DVDNAV_BLOCK_OK,
053        DVDNAV_NOP,
054        DVDNAV_STILL_FRAME,
055        DVDNAV_SPU_STREAM_CHANGE,
056        DVDNAV_AUDIO_STREAM_CHANGE,
057        DVDNAV_VTS_CHANGE,
058        DVDNAV_CELL_CHANGE,
059        DVDNAV_NAV_PACKET,
060        DVDNAV_STOP,
061        DVDNAV_HIGHLIGHT,
062        DVDNAV_SPU_CLUT_CHANGE,
063        DVDNAV_HOP_CHANNEL,
064        DVDNAV_WAIT;
065
066    /**
067     * {@code <dvdread/dvd_reader.h>}
068     */
069    @JniField(flags = { CONSTANT })
070    private static int
071        DVD_VIDEO_LB_LEN,
072        MAX_UDF_FILE_NAME_LEN;
073
074    /**
075     * {@code <fcntl.h>}
076     */
077    @JniField(flags = { CONSTANT })
078    private static int
079        SEEK_SET,
080        SEEK_CUR,
081        SEEK_END;
082
083    private final String path;
084    private final long self;
085    private boolean finished = false;
086    private boolean closed = false;
087    private String title = null;
088    private String serial = null;
089
090    /**
091     * Sole constructor.
092     *
093     * @param   path            The path ({@link String}) to the DVD
094     *                          device.
095     *
096     * @throws  IOException     If the device cannot be opened.
097     */
098    public DVDNav(String path) throws IOException {
099        this.path = Objects.requireNonNull(path);
100
101        long selfp[] = new long[] { 0L };
102
103        if (dvdnav_open(selfp, path) == DVDNAV_STATUS_OK) {
104            self = selfp[0];
105        } else {
106            throw new IOException("Cannot open " + path);
107        }
108
109        if (set_readahead_flag((byte) 0) != DVDNAV_STATUS_OK) {
110            throw new IOException(err_to_string());
111        }
112    }
113
114    protected int set_readahead_flag(byte flag) {
115        return dvdnav_set_readahead_flag(self, flag);
116    }
117
118    protected String err_to_string() { return dvdnav_err_to_string(self); }
119
120    @Synchronized
121    @Override
122    public void close() {
123        if (! closed) {
124            if (self != 0L) {
125                dvdnav_close(self);
126            }
127        }
128    }
129
130    public void setLanguage(String language) throws IOException {
131        if (menu_language_select(language) != DVDNAV_STATUS_OK
132            || audio_language_select(language) != DVDNAV_STATUS_OK
133            || spu_language_select(language) != DVDNAV_STATUS_OK) {
134            throw new IOException(err_to_string());
135        }
136    }
137
138    protected int menu_language_select(String language) {
139        return dvdnav_menu_language_select(self, language);
140    }
141
142    protected int audio_language_select(String language) {
143        return dvdnav_audio_language_select(self, language);
144    }
145
146    protected int spu_language_select(String language) {
147        return dvdnav_spu_language_select(self, language);
148    }
149
150    public String getTitle() {
151        if (title == null) {
152            long[] titlep = new long[] { 0L };
153
154            if (dvdnav_get_title_string(self, titlep) == DVDNAV_STATUS_OK) {
155                title = strdup(titlep[0]);
156            }
157        }
158
159        return title;
160    }
161
162    public String getSerial() {
163        if (serial == null) {
164            long[] serialp = new long[] { 0L };
165
166            if (dvdnav_get_serial_string(self, serialp) == DVDNAV_STATUS_OK) {
167                serial = strdup(serialp[0]);
168            }
169        }
170
171        return serial;
172    }
173
174    @Synchronized
175    public Block getNextBlock() throws IOException {
176        Block block = null;
177
178        if (! finished) {
179            byte[] buf = new byte[DVD_VIDEO_LB_LEN];
180            int[] eventp = new int[] { 0 };
181            int[] lenp = new int[] { 0 };
182
183            if (get_next_block(buf, eventp, lenp) == DVDNAV_STATUS_OK) {
184                block = new Block(buf, eventp[0], lenp[0]);
185                finished |= (block.getEvent() == DVDNAV_STOP);
186            } else {
187                throw new IOException(err_to_string());
188            }
189        }
190
191        return block;
192    }
193
194    protected int get_next_block(byte[] buf, int[] eventp, int[] lenp) {
195        return dvdnav_get_next_block(self, buf, eventp, lenp);
196    }
197
198    @Data
199    public class Block {
200        private final byte[] buf;
201        private final int event;
202        private final int len;
203    }
204
205    private static native int dvdnav_open(@JniArg(cast = "dvdnav_t **") long[] selfp,
206                                          @JniArg(cast = "const char *") String path);
207
208    private static native int dvdnav_dup(@JniArg(cast = "dvdnav_t **") long[] destp,
209                                         @JniArg(cast = "dvdnav_t *") long src);
210
211    private static native int dvdnav_free_dup(@JniArg(cast = "dvdnav_t *") long self);
212
213    private static native int dvdnav_close(@JniArg(cast = "dvdnav_t *") long self);
214
215    private static native int dvdnav_reset(@JniArg(cast = "dvdnav_t *") long self);
216    /*
217     * dvdnav_status_t dvdnav_path(dvdnav_t *self, const char **path);
218     *
219     * private static native int dvdnav_path(@JniArg(cast = "dvdnav_t *") long self, const char **path);
220     */
221    private static native String dvdnav_err_to_string(@JniArg(cast = "dvdnav_t *") long self);
222
223    private static native String dvdnav_version();
224
225    private static native int dvdnav_set_region_mask(@JniArg(cast = "dvdnav_t *") long self,
226                                                     int mask);
227
228    private static native int dvdnav_get_region_mask(@JniArg(cast = "dvdnav_t *") long self,
229                                                     int[] maskp);
230
231    private static native int dvdnav_set_readahead_flag(@JniArg(cast = "dvdnav_t *") long self,
232                                                        int flag);
233
234    private static native int dvdnav_get_readahead_flag(@JniArg(cast = "dvdnav_t *") long self,
235                                                        int[] flagp);
236
237    private static native int dvdnav_set_PGC_positioning_flag(@JniArg(cast = "dvdnav_t *") long self,
238                                                              int flag);
239
240    private static native int dvdnav_get_PGC_positioning_flag(@JniArg(cast = "dvdnav_t *") long self,
241                                                              int[] flagp);
242
243    private static native int dvdnav_get_next_block(@JniArg(cast = "dvdnav_t *") long self,
244                                                    byte[] buf, int[] eventp, int[] lenp);
245    /*
246     * dvdnav_status_t
247     * dvdnav_get_next_cache_block(dvdnav_t *self, uint8_t **buf,
248     *                             int32_t *event, int32_t *len);
249     *
250     * private static native int dvdnav_get_next_cache_block(@JniArg(cast = "dvdnav_t *") long self, uint8_t **buf, int[] eventp, int[] lenp);
251    */
252    /*
253     * dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self,
254     *                                         unsigned char *buf);
255     *
256     * private static native int dvdnav_free_cache_block(@JniArg(cast = "dvdnav_t *") long self, unsigned char *buf);
257     */
258    private static native int dvdnav_still_skip(@JniArg(cast = "dvdnav_t *") long self);
259
260    private static native int dvdnav_wait_skip(@JniArg(cast = "dvdnav_t *") long self);
261
262    private static native int dvdnav_get_next_still_flag(@JniArg(cast = "dvdnav_t *") long self);
263
264    private static native int dvdnav_stop(@JniArg(cast = "dvdnav_t *") long self);
265
266    private static native int dvdnav_get_number_of_titles(@JniArg(cast = "dvdnav_t *") long self,
267                                                          int[] titlesp);
268
269    private static native int dvdnav_get_number_of_parts(@JniArg(cast = "dvdnav_t *") long self,
270                                                         int title, int[] partsp);
271
272    private static native int dvdnav_title_play(@JniArg(cast = "dvdnav_t *") long self,
273                                                int title);
274    /*
275     * Currently unimplemented!
276     * dvdnav_status_t
277     * dvdnav_part_play(dvdnav_t *self, int32_t title, int32_t part);
278     */
279    private static native int dvdnav_program_play(@JniArg(cast = "dvdnav_t *") long self,
280                                                  int title, int pgcn, int pgn);
281    /*
282     * uint32_t
283     * dvdnav_describe_title_chapters(dvdnav_t *self, int32_t title,
284     *                                uint64_t **times, uint64_t *duration);
285     *
286     * private static native int dvdnav_describe_title_chapters(@JniArg(cast = "dvdnav_t *") long self, int title, uint64_t **times, long[] durationp);
287     */
288    /*
289     * Currently unimplemented!
290     * dvdnav_status_t dvdnav_part_play_auto_stop(dvdnav_t *self, int32_t title, int32_t part, int32_t parts_to_play);
291     */
292    /*
293     * Currently unimplemented!
294     * dvdnav_status_t dvdnav_time_play(dvdnav_t *self, int32_t title, uint64_t time);
295     */
296    /*
297     * dvdnav_status_t dvdnav_menu_call(dvdnav_t *self, DVDMenuID_t menu);
298     *
299     * private static native int dvdnav_menu_call(@JniArg(cast = "dvdnav_t *") long self, DVDMenuID_t menu);
300     */
301    private static native int dvdnav_current_title_info(@JniArg(cast = "dvdnav_t *") long self,
302                                                        int[] titlep, int[] partp);
303
304    private static native int dvdnav_current_title_program(@JniArg(cast = "dvdnav_t *") long self,
305                                                           int[] titlep, int[] pgcnp, int[] pgnp);
306    /*
307     * Current implementation is wrong and likely to behave unpredictably!
308     * Use is discouraged!
309     *
310     * dvdnav_status_t
311     * dvdnav_get_position_in_title(dvdnav_t *self,
312     *                              uint32_t *pos, uint32_t *len);
313     */
314    private static native int dvdnav_part_search(@JniArg(cast = "dvdnav_t *") long self,
315                                                 int part);
316
317    private static native int dvdnav_sector_search(@JniArg(cast = "dvdnav_t *") long self,
318                                                   long offset, int origin);
319
320    private static native long dvdnav_get_current_time(@JniArg(cast = "dvdnav_t *") long self);
321
322    private static native int dvdnav_time_search(@JniArg(cast = "dvdnav_t *") long self,
323                                                 long time);
324
325    private static native int dvdnav_go_up(@JniArg(cast = "dvdnav_t *") long self);
326
327    private static native int dvdnav_prev_pg_search(@JniArg(cast = "dvdnav_t *") long self);
328
329    private static native int dvdnav_top_pg_search(@JniArg(cast = "dvdnav_t *") long self);
330
331    private static native int dvdnav_next_pg_search(@JniArg(cast = "dvdnav_t *") long self);
332
333    private static native int dvdnav_get_position(@JniArg(cast = "dvdnav_t *") long self,
334                                                  int[] posp, int[] lenp);
335
336    private static native int dvdnav_get_current_highlight(@JniArg(cast = "dvdnav_t *") long self,
337                                                           int[] buttonp);
338
339    private static native long dvdnav_get_current_nav_pci(@JniArg(cast = "dvdnav_t *") long self);
340
341    private static native long dvdnav_get_current_nav_dsi(@JniArg(cast = "dvdnav_t *") long self);
342    /*
343     * dvdnav_status_t
344     * dvdnav_get_highlight_area(pci_t *nav_pci, int32_t button, int32_t mode,
345     *                           dvdnav_highlight_area_t *highlight);
346     *
347     * private static native int dvdnav_get_highlight_area(@JniArg(cast = "pci_t *") long nav_pci, int button, int mode, dvdnav_highlight_area_t *highlight);
348     */
349    private static native int dvdnav_upper_button_select(@JniArg(cast = "dvdnav_t *") long self,
350                                                         @JniArg(cast = "pci_t *") long pci);
351
352    private static native int dvdnav_lower_button_select(@JniArg(cast = "dvdnav_t *") long self,
353                                                         @JniArg(cast = "pci_t *") long pci);
354
355    private static native int dvdnav_right_button_select(@JniArg(cast = "dvdnav_t *") long self,
356                                                         @JniArg(cast = "pci_t *") long pci);
357
358    private static native int dvdnav_left_button_select(@JniArg(cast = "dvdnav_t *") long self,
359                                                        @JniArg(cast = "pci_t *") long pci);
360
361    private static native int dvdnav_button_activate(@JniArg(cast = "dvdnav_t *") long self,
362                                                     @JniArg(cast = "pci_t *") long pci);
363
364    private static native int dvdnav_button_select(@JniArg(cast = "dvdnav_t *") long self,
365                                                   @JniArg(cast = "pci_t *") long pci, int button);
366
367    private static native int dvdnav_button_select_and_activate(@JniArg(cast = "dvdnav_t *") long self,
368                                                                @JniArg(cast = "pci_t *") long pci, int button);
369    /*
370     * dvdnav_status_t
371     * dvdnav_button_activate_cmd(dvdnav_t *self,
372     *                            int32_t button, vm_cmd_t *cmd);
373     *
374     * private static native int dvdnav_button_activate_cmd(@JniArg(cast = "dvdnav_t *") long self, int button, vm_cmd_t *cmd);
375     */
376    private static native int dvdnav_mouse_select(@JniArg(cast = "dvdnav_t *") long self,
377                                                  @JniArg(cast = "pci_t *") long pci, int x, int y);
378
379    private static native int dvdnav_mouse_activate(@JniArg(cast = "dvdnav_t *") long self,
380                                                    @JniArg(cast = "pci_t *") long pci, int x, int y);
381
382    private static native int dvdnav_menu_language_select(@JniArg(cast = "dvdnav_t *") long self,
383                                                          String code);
384
385    private static native int dvdnav_audio_language_select(@JniArg(cast = "dvdnav_t *") long self,
386                                                           String code);
387
388    private static native int dvdnav_spu_language_select(@JniArg(cast = "dvdnav_t *") long self,
389                                                         String code);
390
391    private static native int dvdnav_get_title_string(@JniArg(cast = "dvdnav_t *") long self,
392                                                      @JniArg(cast = "const char **") long[] titlep);
393
394    private static native int dvdnav_get_serial_string(@JniArg(cast = "dvdnav_t *") long self,
395                                                       @JniArg(cast = "const char **") long[] serialp);
396
397    private static native byte dvdnav_get_video_aspect(@JniArg(cast = "dvdnav_t *") long self);
398
399    private static native int dvdnav_get_video_resolution(@JniArg(cast = "dvdnav_t *") long self,
400                                                          int[] widthp, int[] heightp);
401
402    private static native byte dvdnav_get_video_scale_permission(@JniArg(cast = "dvdnav_t *") long self);
403
404    private static native char dvdnav_audio_stream_to_lang(@JniArg(cast = "dvdnav_t *") long self,
405                                                           byte stream);
406
407    private static native char dvdnav_audio_stream_format(@JniArg(cast = "dvdnav_t *") long self,
408                                                          byte stream);
409
410    private static native char dvdnav_audio_stream_channels(@JniArg(cast = "dvdnav_t *") long self,
411                                                            byte stream);
412
413    private static native char dvdnav_spu_stream_to_lang(@JniArg(cast = "dvdnav_t *") long self,
414                                                         byte stream);
415
416    private static native byte dvdnav_get_audio_logical_stream(@JniArg(cast = "dvdnav_t *") long self,
417                                                               byte audio_num);
418    /*
419     * dvdnav_status_t
420     * dvdnav_get_audio_attr(dvdnav_t *self, uint8_t audio_mum, audio_attr_t *audio_attr);
421     *
422     * private static native int dvdnav_get_audio_attr(@JniArg(cast = "dvdnav_t *") long self, byte audio_mum, audio_attr_t *audio_attr);
423     */
424    private static native byte dvdnav_get_spu_logical_stream(@JniArg(cast = "dvdnav_t *") long self,
425                                                             byte subp_num);
426    /*
427     * dvdnav_status_t
428     * dvdnav_get_spu_attr(dvdnav_t *self, uint8_t audio_mum, subp_attr_t *subp_attr);
429     *
430     * private static native int dvdnav_get_spu_attr(@JniArg(cast = "dvdnav_t *") long self, byte audio_mum, subp_attr_t *subp_attr);
431     */
432    private static native byte dvdnav_get_active_audio_stream(@JniArg(cast = "dvdnav_t *") long self);
433
434    private static native byte dvdnav_get_active_spu_stream(@JniArg(cast = "dvdnav_t *") long self);
435
436    private static native int dvdnav_angle_change(@JniArg(cast = "dvdnav_t *") long self,
437                                                  int angle);
438
439    private static native int dvdnav_get_angle_info(@JniArg(cast = "dvdnav_t *") long self,
440                                                    int[] current_anglep,
441                                                    int[] number_of_anglesp);
442
443    private static native byte dvdnav_is_domain_fp(@JniArg(cast = "dvdnav_t *") long self);
444
445    private static native byte dvdnav_is_domain_vmgm(@JniArg(cast = "dvdnav_t *") long self);
446
447    private static native byte dvdnav_is_domain_vtsm(@JniArg(cast = "dvdnav_t *") long self);
448
449    private static native byte dvdnav_is_domain_vts(@JniArg(cast = "dvdnav_t *") long self);
450
451    private String strdup(long pointer) {
452        String string = null;
453
454        if (pointer != 0L) {
455            int length = strlen(pointer);
456            byte[] bytes = new byte[length];
457
458            memmove(bytes, pointer, bytes.length);
459
460            try {
461                string = new String(bytes, UTF_8.name());
462            } catch (UnsupportedEncodingException exception) {
463                throw new IllegalStateException(exception);
464            }
465        }
466
467        return string;
468    }
469
470    private static native void memmove(@JniArg(cast = "void *", flags = { NO_IN, CRITICAL }) byte[] out,
471                                       @JniArg(cast = "const void *") long in,
472                                       @JniArg(cast = "size_t") long size);
473    private static native int strlen(@JniArg(cast = "char *") long pointer);
474}