[FFmpeg-devel] [PATCH v2] Adds DVD protocol
Lucien Murray-Pitts
lucien.murraypitts at gmail.com
Wed Jan 5 16:04:02 EET 2022
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 at 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 --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, ®ion_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, ×, &_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, ×, &_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;
--
2.31.1
More information about the ffmpeg-devel
mailing list