diff mbox series

[FFmpeg-devel,v3] Adds DVD protocol

Message ID ab71263a-cf01-4f25-2ca1-03712befca35@gmail.com
State New
Headers show
Series [FFmpeg-devel,v3] Adds DVD protocol | expand

Checks

Context Check Description
andriy/commit_msg_x86 warning The first line of the commit message must start with a context terminated by a colon and a space, for example "lavu/opt: " or "doc: ".
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished
andriy/commit_msg_ppc warning The first line of the commit message must start with a context terminated by a colon and a space, for example "lavu/opt: " or "doc: ".
andriy/make_ppc success Make finished
andriy/make_fate_ppc success Make fate finished

Commit Message

Lucien Murray-Pitts Jan. 5, 2022, 2:42 p.m. UTC
Adds DVD protocol

Copies the existing Bluray protocol format to add DVD protocol support using
libdvdnav. Since title selection is mandatory ffprobe cant provide information
for the complete disk but a single title only.  Chapter information for probe
 will also be missing. To see a complete disk catalog of titles/chapters the
tools/dvd2concat perl script should be used.

Signed-off-by: Lucien Murray-Pitts <lucien.murraypitts@gmail.com>
---
 configure               |   4 +
 libavformat/Makefile    |   1 +
 libavformat/dvd.c       | 264 ++++++++++++++++++++++++++++++++++++++++
 libavformat/protocols.c |   1 +
 4 files changed, 270 insertions(+)
 create mode 100644 libavformat/dvd.c
diff mbox series

Patch

diff --git a/configure b/configure
index 8392c26015..7f8b0046d2 100755
--- a/configure
+++ b/configure
@@ -230,6 +230,7 @@  External library support:
   --enable-libdavs2        enable AVS2 decoding via libdavs2 [no]
   --enable-libdc1394       enable IIDC-1394 grabbing using libdc1394
                            and libraw1394 [no]
+  --enable-libdvdnav       enable DVD reading using libdvdnav [no]
   --enable-libfdk-aac      enable AAC de/encoding via libfdk-aac [no]
   --enable-libflite        enable flite (voice synthesis) support via libflite [no]
   --enable-libfontconfig   enable libfontconfig, useful for drawtext filter [no]
@@ -1822,6 +1823,7 @@  EXTERNAL_LIBRARY_LIST="
     libdav1d
     libdc1394
     libdrm
+    libdvdnav
     libflite
     libfontconfig
     libfreetype
@@ -3539,6 +3541,7 @@  xv_outdev_deps="xlib_xv xlib_x11 xlib_xext"
 # protocols
 async_protocol_deps="threads"
 bluray_protocol_deps="libbluray"
+dvd_protocol_deps="libdvdnav"
 ffrtmpcrypt_protocol_conflict="librtmp_protocol"
 ffrtmpcrypt_protocol_deps_any="gcrypt gmp openssl mbedtls"
 ffrtmpcrypt_protocol_select="tcp_protocol"
@@ -6526,6 +6529,7 @@  enabled libdav1d          && require_pkg_config libdav1d "dav1d >= 0.5.0" "dav1d
 enabled libdavs2          && require_pkg_config libdavs2 "davs2 >= 1.6.0" davs2.h davs2_decoder_open
 enabled libdc1394         && require_pkg_config libdc1394 libdc1394-2 dc1394/dc1394.h dc1394_new
 enabled libdrm            && require_pkg_config libdrm libdrm xf86drm.h drmGetVersion
+enabled libdvdnav         && require_pkg_config libdvdnav dvdnav dvdnav/dvdnav.h dvdnav_open
 enabled libfdk_aac        && { check_pkg_config libfdk_aac fdk-aac "fdk-aac/aacenc_lib.h" aacEncOpen ||
                                { require libfdk_aac fdk-aac/aacenc_lib.h aacEncOpen -lfdk-aac &&
                                  warn "using libfdk without pkg-config"; } }
diff --git a/libavformat/Makefile b/libavformat/Makefile
index c479ea998e..5fbba89e36 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -627,6 +627,7 @@  OBJS-$(CONFIG_CONCAT_PROTOCOL)           += concat.o
 OBJS-$(CONFIG_CONCATF_PROTOCOL)          += concat.o
 OBJS-$(CONFIG_CRYPTO_PROTOCOL)           += crypto.o
 OBJS-$(CONFIG_DATA_PROTOCOL)             += data_uri.o
+OBJS-$(CONFIG_DVD_PROTOCOL)              += dvd.o
 OBJS-$(CONFIG_FFRTMPCRYPT_PROTOCOL)      += rtmpcrypt.o rtmpdigest.o rtmpdh.o
 OBJS-$(CONFIG_FFRTMPHTTP_PROTOCOL)       += rtmphttp.o
 OBJS-$(CONFIG_FILE_PROTOCOL)             += file.o
diff --git a/libavformat/dvd.c b/libavformat/dvd.c
new file mode 100644
index 0000000000..b3bc1b95e4
--- /dev/null
+++ b/libavformat/dvd.c
@@ -0,0 +1,264 @@ 
+/*
+ * DVD (libdvdnav) protocol based on BluRay (libbluray) protocol by Petri Hintukainen.
+ *
+ * Copyright (c) 2022 Lucien Murray-Pitts <lucien.murraypitts <at> gmail.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+/*
+ * REFERENCES: https://code.videolan.org/videolan/libdvdnav/-/blob/master/src/dvdnav/dvdnav.h
+ *             https://code.videolan.org/videolan/libdvdnav/-/blob/master/examples/menus.c
+ *
+ * EXAMPLE USE:
+ *   Choose Title 4, map 1/2/3 (video/audio/subtitles), copy the subtitles as is into mkv
+ *   ./ffmpeg -playlist 4 -i dvd:"/mnt/MURDER_SHE_WROTE_TS.S10D1" -map 0:1 -map 0:2 -map 0:3 -vb 20M  -codec:s copy  remuxed-dvd.mkv
+ *
+ *   Probe for info
+ *   ./ffprobe -loglevel trace -fdebug 8 -playlist 19 -i dvd:"/mnt/MURDER_SHE_WROTE_TS.S10D1" -threads 0 -v warning -print_format json -show_streams -show_chapters -show_format
+ */
+#include <dvdnav/dvdnav.h>
+
+#include "libavutil/avstring.h"
+#include "libavformat/avformat.h"
+#include "libavformat/url.h"
+#include "libavutil/opt.h"
+
+#define DVD_PROTO_PREFIX     "dvd:"
+
+typedef struct {
+    const AVClass *class;
+
+    dvdnav_t *dvdnav;
+
+    int playlist;
+    int angle;
+    int chapter;
+    /*int region;*/
+} DVDContext;
+
+#define OFFSET(x) offsetof(DVDContext, x)
+static const AVOption options[] = {
+{"playlist", "DVD title to play", OFFSET(playlist), AV_OPT_TYPE_INT, { .i64=-1 }, -1,  99999, AV_OPT_FLAG_DECODING_PARAM },
+{"angle",    "DVD Video stream angle", OFFSET(angle),    AV_OPT_TYPE_INT, { .i64=0 },   0,   0xfe, AV_OPT_FLAG_DECODING_PARAM },
+{"chapter",  "DVD Chapter to play", OFFSET(chapter),  AV_OPT_TYPE_INT, { .i64=1 },   1, 0xfffe, AV_OPT_FLAG_DECODING_PARAM },
+/*{"region",   "dvd player region code (1 = region A, 2 = region B, 4 = region C)", OFFSET(region), AV_OPT_TYPE_INT, { .i64=0 }, 0, 3, AV_OPT_FLAG_DECODING_PARAM },*/
+{NULL}
+};
+
+static const AVClass dvd_context_class = {
+    .class_name     = "dvd",
+    .item_name      = av_default_item_name,
+    .option         = options,
+    .version        = LIBAVUTIL_VERSION_INT,
+};
+
+
+static int check_disc_info(URLContext *h)
+{
+    DVDContext *disk = h->priv_data;
+
+    int32_t region_mask;
+
+    // Gets the DVD DISK's region; confirms if disk is at least readable
+    if ( !dvdnav_get_region_mask( disk->dvdnav, &region_mask) ) {
+        av_log(h, AV_LOG_ERROR, "dvdnav_get_region_mask() failed\n");
+        return -1;
+    }
+
+    av_log(h, AV_LOG_INFO, "dvdnav_get_region_mask() got %i\n", region_mask);
+
+    return 0;
+}
+
+static int dvd_close(URLContext *h)
+{
+    DVDContext *disk = h->priv_data;
+    if (disk->dvdnav) {
+        dvdnav_close( disk->dvdnav );
+    }
+
+    return 0;
+}
+
+static int dvd_open(URLContext *h, const char *path, int flags)
+{
+    DVDContext *disk = h->priv_data;
+    int num_title_idx;
+    const char *diskname = path;
+
+    av_strstart(path, DVD_PROTO_PREFIX, &diskname);
+
+    if ( dvdnav_open(&disk->dvdnav, diskname) != DVDNAV_STATUS_OK ) {
+        av_log(h, AV_LOG_ERROR, "dvdnav_open() failed, problem opening DVD folder/drive\n");
+        return AVERROR(EIO);
+    }
+
+    /* check if disc can be played */
+    if (check_disc_info(h) < 0) {
+        return AVERROR(EIO);
+    }
+
+    /* setup player registers */
+    /* region code has no effect without menus
+    if (disk->region > 0 && disk->region < 5) {
+        av_log(h, AV_LOG_INFO, "setting region code to %d (%c)\n", disk->region, 'A' + (disk->region - 1));
+    }
+    */
+
+    /* load title list */
+    dvdnav_get_number_of_titles(disk->dvdnav, &num_title_idx);
+    if (num_title_idx < 1) {
+        av_log(h, AV_LOG_ERROR, "no usable dvd titles found on disk\n");
+        return AVERROR(EIO);
+    }
+
+    av_log(h, AV_LOG_INFO, "%d usable dvd titles\n", num_title_idx);
+
+    /* if playlist was not given, select longest playlist */
+    if (disk->playlist < 0) {
+        uint64_t duration = 0;
+        int i;
+        for (i = 1; i <= num_title_idx; i++) {
+            uint64_t *times ;
+            uint64_t _duration;
+
+            uint32_t chcount = dvdnav_describe_title_chapters( disk->dvdnav, i, &times, &_duration );
+            if( chcount==0 ) continue;
+
+            // Title complete length
+            av_log(h, AV_LOG_INFO, "title #%05d (%d:%02d:%02d)\n",
+                   i,
+                   ((int)(_duration / 90000) / 3600),
+                   ((int)(_duration / 90000) % 3600) / 60,
+                   ((int)(_duration / 90000) % 60));
+
+            // Display Chapter times
+            for( int tidx=0 ; tidx<chcount; tidx++ ) {
+                if(times==0) break;
+
+                if(times[tidx]==0) break;
+                av_log(h, AV_LOG_INFO, "  chapter %05d (%d:%02d:%02d)\n",
+                   tidx+2,
+                   ((int)(times[tidx] / 90000) / 3600),
+                   ((int)(times[tidx] / 90000) % 3600) / 60,
+                   ((int)(times[tidx] / 90000) % 60));
+            }
+
+            if (_duration > duration) {
+                disk->playlist = i;
+                duration = _duration;
+            }
+
+            free(times);
+        }
+    }
+    av_log(h, AV_LOG_INFO, "selected title# %05d\n", disk->playlist);
+
+    /* select playlist */
+    if ( dvdnav_title_play(disk->dvdnav, disk->playlist) <= 0) {
+        av_log(h, AV_LOG_ERROR, "dvdnav_title_play(%05d) failed, title doesnt exist or is corrupted.\n", disk->playlist);
+        return AVERROR(EIO);
+    }
+
+
+    /* select angle; note angles may appear during video stream and not at the start  */
+    if (disk->angle >= 0) {
+        if( dvdnav_angle_change(disk->dvdnav, disk->angle) ) {
+            av_log(h, AV_LOG_ERROR, "selected angle doesnt exist\n");
+        }
+    }
+
+
+    /* select chapter */
+    if (disk->chapter > 1) {
+        uint64_t *times ;
+        uint64_t _duration;
+
+        uint32_t chcount = dvdnav_describe_title_chapters( disk->dvdnav, disk->playlist, &times, &_duration );
+        if(chcount == 0) {
+            av_log(h, AV_LOG_ERROR, "Title %05d description failed, title doesnt exist or is corrupted.\n", disk->playlist);
+            return AVERROR(EIO);
+        }
+
+        if(disk->chapter > chcount) {
+            av_log(h, AV_LOG_ERROR, "Chapter %05d is out of range of 1..%05d for title %05d.\n", disk->chapter, chcount, disk->playlist);
+            return AVERROR(EIO);
+        }
+
+        av_log(h, AV_LOG_INFO, "  Seeking to: %05ld pts, start of chapter %05d\n", times[disk->chapter - 2], disk->chapter );
+        dvdnav_time_search(disk->dvdnav, times[disk->chapter - 2] );
+
+        free(times);
+    }
+
+    return 0;
+}
+
+static int dvd_read(URLContext *h, unsigned char *buf, int size)
+{
+    DVDContext *disk = h->priv_data;
+    int32_t event;
+    int len;
+
+    if (!disk || !disk->dvdnav) {
+        return AVERROR(EFAULT);
+    }
+
+    dvdnav_get_next_block(disk->dvdnav, (uint8_t *)buf, &event, &len);
+
+    return len == 0 ? AVERROR_EOF : len;
+}
+
+static int64_t dvd_seek(URLContext *h, int64_t pos, int whence)
+{
+    DVDContext *disk = h->priv_data;
+    uint32_t curpos;
+    uint32_t len ;
+
+    if (!disk || !disk->dvdnav) {
+        return AVERROR(EFAULT);
+    }
+
+    switch (whence) {
+    case SEEK_SET:
+    case SEEK_CUR:
+    case SEEK_END:
+        dvdnav_get_position_in_title(disk->dvdnav, &curpos, &len);
+        dvdnav_sector_search(disk->dvdnav, pos, whence);
+        return curpos;
+
+    case AVSEEK_SIZE:
+        dvdnav_get_position_in_title(disk->dvdnav, &curpos, &len);
+        return len ;
+    }
+
+    av_log(h, AV_LOG_ERROR, "Unsupported whence operation %d\n", whence);
+    return AVERROR(EINVAL);
+}
+
+
+const URLProtocol ff_dvd_protocol = {
+    .name            = "dvd",
+    .url_close       = dvd_close,
+    .url_open        = dvd_open,
+    .url_read        = dvd_read,
+    .url_seek        = dvd_seek,
+    .priv_data_size  = sizeof(DVDContext),
+    .priv_data_class = &dvd_context_class,
+};
diff --git a/libavformat/protocols.c b/libavformat/protocols.c
index 948fae411f..4cbcedb5c8 100644
--- a/libavformat/protocols.c
+++ b/libavformat/protocols.c
@@ -30,6 +30,7 @@  extern const URLProtocol ff_concat_protocol;
 extern const URLProtocol ff_concatf_protocol;
 extern const URLProtocol ff_crypto_protocol;
 extern const URLProtocol ff_data_protocol;
+extern const URLProtocol ff_dvd_protocol;
 extern const URLProtocol ff_ffrtmpcrypt_protocol;
 extern const URLProtocol ff_ffrtmphttp_protocol;
 extern const URLProtocol ff_file_protocol;