From 61c056dc2ac4aa1e412a310cc1f158f8ab9674bc Mon Sep 17 00:00:00 2001
From: Lawrence Rust <lvr@softsystem.co.uk>
Date: Sat, 26 Nov 2011 21:22:43 +0100
Subject: [PATCH 029/116] libmythtv: Play encrypted dvd's and iso images from storage groups

This change enables the backend to use libdvdcss to decrypt dvd's and iso
images before sending the blocks over the myth protocol to the frontend.

This replaces the current POSIX file read in RingBuffer with a class like
DVDRingBufferPriv that uses libdvdread to decrypt the raw blocks.

The real heart of the change is in creating a list of blocks that might need
decrypting and then in safe_read() lookup the blocks requested in that list
and decrypt them if necessary.  Unfortunately the lookup is necessary since
if a block is unencrypted, like ifo files, then the decryption operation
corrupts the data.

Signed-off-by: Lawrence Rust <lvr@softsystem.co.uk>
---
 mythtv/libs/libmythdvdnav/dvdread/dvd_input.c  |   10 +-
 mythtv/libs/libmythdvdnav/dvdread/dvd_input.h  |    3 +-
 mythtv/libs/libmythdvdnav/dvdread/dvd_reader.c |   10 +-
 mythtv/libs/libmythtv/dvdstream.cpp            |  281 ++++++++++++++++++++++++
 mythtv/libs/libmythtv/dvdstream.h              |   51 +++++
 mythtv/libs/libmythtv/libmythtv.pro            |    4 +
 mythtv/libs/libmythtv/ringbuffer.cpp           |   12 +
 7 files changed, 360 insertions(+), 11 deletions(-)
 create mode 100644 mythtv/libs/libmythtv/dvdstream.cpp
 create mode 100644 mythtv/libs/libmythtv/dvdstream.h

diff --git a/mythtv/libs/libmythdvdnav/dvdread/dvd_input.c b/mythtv/libs/libmythdvdnav/dvdread/dvd_input.c
index 99cb76f..bf8d485 100644
--- a/mythtv/libs/libmythdvdnav/dvdread/dvd_input.c
+++ b/mythtv/libs/libmythdvdnav/dvdread/dvd_input.c
@@ -33,7 +33,7 @@
 /* The function pointers that is the exported interface of this file. */
 dvd_input_t (*dvdinput_open)  (const char *);
 int         (*dvdinput_close) (dvd_input_t);
-int         (*dvdinput_seek)  (dvd_input_t, int);
+int         (*dvdinput_seek)  (dvd_input_t, int, int);
 int         (*dvdinput_title) (dvd_input_t, int);
 int         (*dvdinput_read)  (dvd_input_t, void *, int, int);
 char *      (*dvdinput_error) (dvd_input_t);
@@ -116,10 +116,9 @@ static char *css_error(dvd_input_t dev)
 /**
  * seek into the device.
  */
-static int css_seek(dvd_input_t dev, int blocks)
+static int css_seek(dvd_input_t dev, int blocks, int flags)
 {
-  /* DVDINPUT_NOFLAGS should match the DVDCSS_NOFLAGS value. */
-  return DVDcss_seek(dev->dvdcss, blocks, DVDINPUT_NOFLAGS);
+  return DVDcss_seek(dev->dvdcss, blocks, flags);
 }
 
 /**
@@ -196,9 +195,10 @@ static char *file_error(dvd_input_t dev)
 /**
  * seek into the device.
  */
-static int file_seek(dvd_input_t dev, int blocks)
+static int file_seek(dvd_input_t dev, int blocks, int flags)
 {
   off_t pos;
+  (void)flags;
 
   pos = mythfile_seek(dev->fd, (off_t)blocks * (off_t)DVD_VIDEO_LB_LEN, SEEK_SET);
   if(pos < 0) {
diff --git a/mythtv/libs/libmythdvdnav/dvdread/dvd_input.h b/mythtv/libs/libmythdvdnav/dvdread/dvd_input.h
index 208512e..73b0c95 100644
--- a/mythtv/libs/libmythdvdnav/dvdread/dvd_input.h
+++ b/mythtv/libs/libmythdvdnav/dvdread/dvd_input.h
@@ -28,6 +28,7 @@
 #define DVDINPUT_NOFLAGS         0
 
 #define DVDINPUT_READ_DECRYPT    (1 << 0)
+#define DVDCSS_SEEK_KEY          (1 << 1)
 
 typedef struct dvd_input_s *dvd_input_t;
 
@@ -50,7 +51,7 @@ typedef struct dvd_input_s *dvd_input_t;
  */
 extern dvd_input_t (*dvdinput_open)  (const char *);
 extern int         (*dvdinput_close) (dvd_input_t);
-extern int         (*dvdinput_seek)  (dvd_input_t, int);
+extern int         (*dvdinput_seek)  (dvd_input_t, int, int);
 extern int         (*dvdinput_title) (dvd_input_t, int);
 extern int         (*dvdinput_read)  (dvd_input_t, void *, int, int);
 extern char *      (*dvdinput_error) (dvd_input_t);
diff --git a/mythtv/libs/libmythdvdnav/dvdread/dvd_reader.c b/mythtv/libs/libmythdvdnav/dvdread/dvd_reader.c
index fd00d6c..69f3a14 100644
--- a/mythtv/libs/libmythdvdnav/dvdread/dvd_reader.c
+++ b/mythtv/libs/libmythdvdnav/dvdread/dvd_reader.c
@@ -1118,14 +1118,14 @@ int UDFReadBlocksRaw( dvd_reader_t *device, uint32_t lb_number,
     return 0;
   }
 
-  ret = dvdinput_seek( device->dev, (int) lb_number );
+  ret = dvdinput_seek( device->dev, (int) lb_number, encrypted & DVDCSS_SEEK_KEY );
   if( ret != (int) lb_number ) {
     fprintf( stderr, "libdvdread: Can't seek to block %u\n", lb_number );
     return 0;
   }
 
   ret = dvdinput_read( device->dev, (char *) data,
-                       (int) block_count, encrypted );
+                       (int) block_count, encrypted & DVDINPUT_READ_DECRYPT );
   return ret;
 }
 
@@ -1163,7 +1163,7 @@ static int DVDReadBlocksPath( dvd_file_t *dvd_file, unsigned int offset,
 
     if( offset < dvd_file->title_sizes[ i ] ) {
       if( ( offset + block_count ) <= dvd_file->title_sizes[ i ] ) {
-        off = dvdinput_seek( dvd_file->title_devs[ i ], (int)offset );
+        off = dvdinput_seek( dvd_file->title_devs[ i ], (int)offset, DVDINPUT_NOFLAGS );
         if( off < 0 || off != (int)offset ) {
           fprintf( stderr, "libdvdread: Can't seek to block %d\n",
                    offset );
@@ -1178,7 +1178,7 @@ static int DVDReadBlocksPath( dvd_file_t *dvd_file, unsigned int offset,
          * (This is only true if you try and read >1GB at a time) */
 
         /* Read part 1 */
-        off = dvdinput_seek( dvd_file->title_devs[ i ], (int)offset );
+        off = dvdinput_seek( dvd_file->title_devs[ i ], (int)offset, DVDINPUT_NOFLAGS );
         if( off < 0 || off != (int)offset ) {
           fprintf( stderr, "libdvdread: Can't seek to block %d\n",
                    offset );
@@ -1195,7 +1195,7 @@ static int DVDReadBlocksPath( dvd_file_t *dvd_file, unsigned int offset,
           return ret;
 
         /* Read part 2 */
-        off = dvdinput_seek( dvd_file->title_devs[ i + 1 ], 0 );
+        off = dvdinput_seek( dvd_file->title_devs[ i + 1 ], 0, DVDINPUT_NOFLAGS );
         if( off < 0 || off != 0 ) {
           fprintf( stderr, "libdvdread: Can't seek to block %d\n",
                    0 );
diff --git a/mythtv/libs/libmythtv/dvdstream.cpp b/mythtv/libs/libmythtv/dvdstream.cpp
new file mode 100644
index 0000000..ba2aa97
--- /dev/null
+++ b/mythtv/libs/libmythtv/dvdstream.cpp
@@ -0,0 +1,281 @@
+/* DVD stream
+ * Copyright 2011 Lawrence Rust <lvr at softsystem dot co dot uk>
+ */
+#include "dvdstream.h"
+
+#include <stdio.h>
+
+#include <QMutexLocker>
+#include <QtAlgorithms>
+
+#include "dvdread/dvd_reader.h"
+#include "dvdread/dvd_udf.h"    // for UDFFindFile
+extern "C" {
+#include "dvdread/dvd_input.h"  // for DVDINPUT_READ_DECRYPT & DVDCSS_SEEK_KEY
+}
+
+#include "mythlogging.h"
+
+
+// A range of block numbers
+class DVDStream::BlockRange
+{
+    uint32_t start, end;
+    int title;
+
+public:
+    BlockRange(uint32_t b, uint32_t n, int t) : start(b), end(b+n), title(t) { }
+
+    bool operator < (const BlockRange& rhs) const { return end <= rhs.start; }
+
+    uint32_t Start() const { return start; }
+    uint32_t End() const { return end; }
+    int Title() const { return title; }
+};
+
+
+// Private but located in/shared with dvd_reader.c
+extern "C" int UDFReadBlocksRaw( dvd_reader_t *device, uint32_t lb_number,
+                             size_t block_count, unsigned char *data,
+                             int encrypted );
+
+
+// Roundup bytes to DVD blocks
+inline uint32_t Len2Blocks(uint32_t len)
+{
+    return (len + (DVD_VIDEO_LB_LEN - 1)) / DVD_VIDEO_LB_LEN;
+}
+
+DVDStream::DVDStream(const QString& filename)
+: RingBuffer(kRingBuffer_File), m_reader(0), m_start(0), m_pos(0), m_title(-1)
+{
+    OpenFile(filename);
+}
+
+DVDStream::~DVDStream()
+{
+    rwlock.lockForWrite();
+
+    if (m_reader)
+        DVDClose(m_reader);
+
+    rwlock.unlock();
+}
+
+bool DVDStream::OpenFile(const QString &filename, uint /*retry_ms*/)
+{
+    rwlock.lockForWrite();
+
+    const QString root = filename.section("/VIDEO_TS/", 0, 0);
+    const QString path = filename.section(root, 1);
+
+    if (m_reader)
+        DVDClose(m_reader);
+
+    m_reader = DVDOpen(qPrintable(root));
+    if (!m_reader)
+    {
+        LOG(VB_GENERAL, LOG_ERR, QString("DVDStream DVDOpen(%1) failed").arg(filename));
+        rwlock.unlock();
+        return false;
+    }
+
+    if (!path.isEmpty())
+    {
+        // Locate the start block of the requested title
+        uint32_t len;
+        m_start = UDFFindFile(m_reader, const_cast<char*>(qPrintable(path)), &len);
+        if (m_start == 0)
+        {
+            LOG(VB_GENERAL, LOG_ERR, QString("DVDStream(%1) UDFFindFile(%2) failed").
+                arg(root).arg(path));
+            DVDClose(m_reader);
+            m_reader = 0;
+            rwlock.unlock();
+            return false;
+        }
+        else
+        {
+            m_list.append(BlockRange(0, Len2Blocks(len), 0));
+        }
+    }
+    else
+    {
+        // Create a list of the possibly encrypted files
+        uint32_t len, start;
+
+        // Root menu
+        char name[64] = "VIDEO_TS/VIDEO_TS.VOB";
+        start = UDFFindFile(m_reader, name, &len);
+        if( start != 0 && len != 0 )
+            m_list.append(BlockRange(start, Len2Blocks(len), 0));
+
+        const int kTitles = 100;
+        for ( int title = 1; title < kTitles; ++title)
+        {
+            // Menu
+            snprintf(name, sizeof name, "/VIDEO_TS/VTS_%02d_0.VOB", title);
+            start = UDFFindFile(m_reader, name, &len);
+            if( start != 0 && len != 0 )
+                m_list.append(BlockRange(start, Len2Blocks(len), title));
+
+            for ( int part = 1; part < 10; ++part)
+            {
+                // A/V track
+                snprintf(name, sizeof name, "/VIDEO_TS/VTS_%02d_%d.VOB", title, part);
+                start = UDFFindFile(m_reader, name, &len);
+                if( start != 0 && len != 0 )
+                    m_list.append(BlockRange(start, Len2Blocks(len), title + part * kTitles));
+            }
+        }
+
+        qSort( m_list);
+
+        // Open the root menu so that CSS keys are generated now
+        dvd_file_t *file = DVDOpenFile(m_reader, 0, DVD_READ_MENU_VOBS);
+        if (file)
+            DVDCloseFile(file);
+        else
+            LOG(VB_GENERAL, LOG_ERR, "DVDStream DVDOpenFile(VOBS_1) failed");
+    }
+
+    rwlock.unlock();
+    return true;
+}
+
+//virtual
+bool DVDStream::IsOpen(void) const
+{
+    rwlock.lockForRead();
+    bool ret = m_reader != 0;
+    rwlock.unlock();
+    return ret;
+}
+
+//virtual
+int DVDStream::safe_read(void *data, uint size)
+{
+    uint32_t lb = size / DVD_VIDEO_LB_LEN;
+    if (lb < 1)
+    {
+        LOG(VB_GENERAL, LOG_ERR, "DVDStream::safe_read too small");
+        return -1;
+    }
+
+    if (!m_reader)
+        return -1;
+
+    int ret = 0;
+
+    // Are any blocks in the range encrypted?
+    list_t::const_iterator it = qBinaryFind(m_list, BlockRange(m_pos, lb, -1));
+    uint32_t b = it == m_list.end() ? lb : m_pos < it->Start() ? it->Start() - m_pos : 0;
+    if (b)
+    {
+        // Read the beginning unencrypted blocks
+        ret = UDFReadBlocksRaw(m_reader, m_pos, b, (unsigned char*)data, DVDINPUT_NOFLAGS);
+        if (ret == -1)
+        {
+            LOG(VB_GENERAL, LOG_ERR, "DVDStream::safe_read DVDReadBlocks error");
+            return -1;
+        }
+
+        m_pos += ret;
+        lb -= ret;
+        if (it == m_list.end())
+            return ret * DVD_VIDEO_LB_LEN;
+
+        data = (unsigned char*)data + ret * DVD_VIDEO_LB_LEN;
+    }
+
+    b = it->End() - m_pos;
+    if (b > lb)
+        b = lb;
+
+    // Request new key if change in title
+    int flags = DVDINPUT_READ_DECRYPT;
+    if (it->Title() != m_title)
+    {
+        m_title = it->Title();
+        flags |= DVDCSS_SEEK_KEY;
+    }
+
+    // Read the encrypted blocks
+    int ret2 = UDFReadBlocksRaw(m_reader, m_pos + m_start, b, (unsigned char*)data, flags);
+    if (ret2 == -1)
+    {
+        LOG(VB_GENERAL, LOG_ERR, "DVDStream::safe_read DVDReadBlocks error");
+        m_title = -1;
+        return -1;
+    }
+
+    m_pos += ret2;
+    ret += ret2;
+    lb -= ret2;
+    data = (unsigned char*)data + ret2 * DVD_VIDEO_LB_LEN;
+
+    if (lb > 0 && m_start == 0)
+    {
+        // Read the last unencrypted blocks
+        ret2 = UDFReadBlocksRaw(m_reader, m_pos, lb, (unsigned char*)data, DVDINPUT_NOFLAGS);
+        if (ret2 == -1)
+        {
+            LOG(VB_GENERAL, LOG_ERR, "DVDStream::safe_read DVDReadBlocks error");
+            return -1;
+        }
+
+        m_pos += ret2;
+        ret += ret2;;
+    }
+
+    return ret * DVD_VIDEO_LB_LEN;
+}
+
+//virtual
+long long DVDStream::Seek(long long pos, int whence, bool has_lock)
+{
+    if (!m_reader)
+        return -1;
+
+    if (SEEK_END == whence)
+    {
+        errno = EINVAL;
+        return -1;
+    }
+
+    uint32_t lb = pos / DVD_VIDEO_LB_LEN;
+    if ((qlonglong)lb * DVD_VIDEO_LB_LEN != pos)
+    {
+        LOG(VB_GENERAL, LOG_ERR, "DVDStream::Seek not block aligned");
+        return -1;
+    }
+
+    // lockForWrite takes priority over lockForRead, so this will
+    // take priority over the lockForRead in the read ahead thread.
+    if (!has_lock)
+        rwlock.lockForWrite();
+
+    poslock.lockForWrite();
+
+    m_pos = lb;
+
+    poslock.unlock();
+
+    generalWait.wakeAll();
+
+    if (!has_lock)
+        rwlock.unlock();
+
+    return pos;
+}
+
+//virtual 
+long long DVDStream::GetReadPosition(void)  const
+{
+    poslock.lockForRead();
+    long long ret = (long long)m_pos * DVD_VIDEO_LB_LEN;
+    poslock.unlock();
+    return ret;
+}
+
+// End of dvdstream,.cpp
diff --git a/mythtv/libs/libmythtv/dvdstream.h b/mythtv/libs/libmythtv/dvdstream.h
new file mode 100644
index 0000000..293a69d
--- /dev/null
+++ b/mythtv/libs/libmythtv/dvdstream.h
@@ -0,0 +1,51 @@
+/* DVD stream
+ * Copyright 2011 Lawrence Rust <lvr at softsystem dot co dot uk>
+ */
+#ifndef DVDSTREAM_H
+#define DVDSTREAM_H
+
+#include <stdint.h>
+
+#include <QString>
+#include <QList>
+
+#include "ringbuffer.h"
+
+typedef struct dvd_reader_s dvd_reader_t;
+
+
+/**
+ * Stream content from a DVD image file
+ */
+class MTV_PUBLIC DVDStream : public RingBuffer
+{
+    Q_DISABLE_COPY(DVDStream)
+
+public:
+    DVDStream(const QString&);
+    virtual ~DVDStream();
+
+public:
+    // RingBuffer methods
+    virtual long long GetReadPosition(void)  const;
+    virtual bool IsOpen(void) const;
+    virtual bool OpenFile(const QString &lfilename, uint retry_ms = 0);
+    virtual long long Seek(long long pos, int whence, bool has_lock);
+
+protected:
+    virtual int safe_read(void *data, uint sz);
+
+    // Implementation
+private:
+    dvd_reader_t *m_reader;
+    uint32_t m_start;
+
+    class BlockRange;
+    typedef QList<BlockRange> list_t;
+    list_t m_list;          // List of possibly encryoted block ranges
+
+    uint32_t m_pos;         // Current read position (blocks)
+    int m_title;            // Last title decrypted
+};
+
+#endif /* ndef DVDSTREAM_H */
diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro
index fcf1170..5b12711 100644
--- a/mythtv/libs/libmythtv/libmythtv.pro
+++ b/mythtv/libs/libmythtv/libmythtv.pro
@@ -237,6 +237,9 @@ HEADERS += channelscan/iptvchannelfetcher.h
 SOURCES += channelscan/scaninfo.cpp channelscan/channelimporter.cpp
 SOURCES += channelscan/iptvchannelfetcher.cpp
 
+HEADERS += dvdstream.h
+SOURCES += dvdstream.cpp
+
 # subtitles: srt
 HEADERS += srtwriter.h
 SOURCES += srtwriter.cpp
@@ -249,6 +252,7 @@ INSTALLS += inc
 
 #DVD stuff
 DEPENDPATH  += ../libmythdvdnav/
+DEPENDPATH  += ../libmythdvdnav/dvdread # for dvd_reader.h & dvd_input.h
 INCLUDEPATH += ../libmythdvdnav
 POST_TARGETDEPS += ../libmythdvdnav/libmythdvdnav-$${MYTH_LIB_EXT}
 HEADERS += DVD/dvdringbuffer.h
diff --git a/mythtv/libs/libmythtv/ringbuffer.cpp b/mythtv/libs/libmythtv/ringbuffer.cpp
index b4f0e76..cff6e4d 100644
--- a/mythtv/libs/libmythtv/ringbuffer.cpp
+++ b/mythtv/libs/libmythtv/ringbuffer.cpp
@@ -17,6 +17,7 @@
 #include "ThreadedFileWriter.h"
 #include "fileringbuffer.h"
 #include "streamingringbuffer.h"
+#include "dvdstream.h"
 #include "livetvchain.h"
 #include "mythcontext.h"
 #include "ringbuffer.h"
@@ -171,6 +172,17 @@ RingBuffer *RingBuffer::Create(
         return new BDRingBuffer(lfilename);
     }
 
+    if (!mythurl && dvdext)
+    {
+        LOG(VB_PLAYBACK, LOG_INFO, "ISO image at " + lfilename);
+        return new DVDStream(lfilename);
+    }
+    if (!mythurl && lower.endsWith(".vob") && lfilename.contains("/VIDEO_TS/"))
+    {
+        LOG(VB_PLAYBACK, LOG_INFO, "DVD VOB at " + lfilename);
+        return new DVDStream(lfilename);
+    }
+
     return new FileRingBuffer(
         lfilename, write, usereadahead, timeout_ms);
 }
-- 
1.7.4.1

