Skip to content

Commit

Permalink
Implement dvdcss_readv in DvdCss.readv()
Browse files Browse the repository at this point in the history
  • Loading branch information
rlaphoenix committed Jul 20, 2024
1 parent a530c81 commit c2c8e19
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 24 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ Python 3.8 has been dropped, but 3.12 is now supported. Unfortunately this means
dropping support for Windows 7/8/8.1 as well. This was more or less necessary to keep
up with the latest versions of other dependencies as well as crucial bug fixes.

### Added

- Implemented `libdvdcss.dvdcss_readv()` to `DvdCss.readv()`. Simply create buffers
using `create_string_buffer(b"", 2048)` and pass them as arguments. Read data will
go into the original buffers made. You can read the bytes from the `raw` property
of each string buffer.

### Fixed

- Various CI and linting tooling mistakes and made it more efficient.
Expand Down
18 changes: 1 addition & 17 deletions docs/todo.rst
Original file line number Diff line number Diff line change
@@ -1,20 +1,4 @@
To-do
=====

- Implement dvdcss_readv, not sure how this would be used or implemented.

Completed
---------

- Implement dvdcss_open.
- Implement dvdcss_close.
- Implement dvdcss_seek.
- Implement dvdcss_read.
- Implement dvdcss_error.
- Implement dvdcss_is_scrambled.
- Implement `__enter__` and `__exit__` for with() disposing support.
- Add handlers for failed find_library calls.
- Add handlers for failed cdll calls.
- Add instructions for installing libdvdcss.
- Add function to set DVDCSS_VERBOSE.
- Add function to set DVDCSS_METHOD.
- Implement ``dvdcss_open_stream()`` for accessing DVDs in custom ways, e.g. over the network.
82 changes: 76 additions & 6 deletions pydvdcss/dvdcss.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,27 @@

from ctypes import (
CDLL,
POINTER,
Array,
c_char,
c_char_p,
c_int,
c_void_p,
create_string_buffer,
)
from ctypes.util import find_library
from pathlib import Path
from typing import Any
from typing import Any, Literal

from typing_extensions import deprecated

from pydvdcss import constants, exceptions, types
from pydvdcss.structs import (
DvdCssStreamCb,
Iovec,
ReadFlag,
SeekFlag,
iovecs,
)
from pydvdcss.utilities import message_with_error

Expand Down Expand Up @@ -207,7 +212,7 @@ def seek(self, sector: int, flag: types.SeekFlag_T = SeekFlag.Unset) -> int:

def read(self, sectors: int, flag: types.ReadFlag_T = ReadFlag.Unset) -> bytes:
"""
Read from the DVD device or directory and decrypt data if requested.
Read from the DVD device or directory.
The flag is used to indicate when the library should decrypt VOB data with it's
CSS title key.
Expand All @@ -229,7 +234,7 @@ def read(self, sectors: int, flag: types.ReadFlag_T = ReadFlag.Unset) -> bytes:
NoDeviceError: No DVD device or directory is open yet.
ReadError: Failure reading sectors, or returned data is less than expected.
Returns the read logical blocks, or raises an IOError if a reading error occurs.
Returns the read logical blocks.
"""
if self.handle is None:
raise exceptions.NoDeviceError(
Expand Down Expand Up @@ -275,9 +280,72 @@ def read(self, sectors: int, flag: types.ReadFlag_T = ReadFlag.Unset) -> bytes:

return data

# def readv(self, p_iovec, i_blocks, i_flags):
# TODO: Implement readv, not sure how this would be used or implemented.
# It's possible the need for readv via python is simply unnecessary.
def readv(
self, *buffers: Array[c_char], decrypt: bool | Literal[0, 1] = False
) -> int:
"""
Read from the DVD device or directory to multiple buffers.
You must seek to the start of each title and/or through VOB data sectors to
get the title keys necessary to descramble/decrypt CSS. This will NOT error
if you read a scrambled VOB sector with no title key to descramble with.
Generally, you should seek through the disc first to get the keys, then seek
back to the start and then begin reading.
Parameters:
buffers: Buffers to write to. Buffer lengths must be multiples of a sector
(2048 bytes).
decrypt: Whether to decrypt the read sectors, if encrypted. Only VOB data
sectors can be scrambled. It is safe to always use True.
Raises:
NoDeviceError: No DVD device or directory is open yet.
ReadError: Failure reading sectors, or returned data is less than expected.
Returns the read logical blocks within the buffers.
"""
if self.handle is None:
raise exceptions.NoDeviceError(
"No DVD device or directory is open yet, use open() first."
)

unsupported_buffers = [
buffer for buffer in buffers if not isinstance(buffer, Array)
]
if unsupported_buffers:
raise TypeError(
"Expected all buffers to be an Array[c_char], but at least one wasn't.",
unsupported_buffers,
)

if isinstance(decrypt, bool):
decrypt = 1 if decrypt else 0
elif decrypt not in (0, 1):
raise TypeError(
f"Expected decrypt to be a bool or int-bool, not {decrypt!r}"
)

buffer_len = sum(map(len, buffers))
if buffer_len % constants.SECTOR_SIZE != 0:
raise exceptions.PyDvdCssError(
f"Buffers must read a multiple of 1 sector "
f"({constants.SECTOR_SIZE} bytes), tried to read {buffer_len} bytes"
)

sectors = buffer_len // constants.SECTOR_SIZE

read_sectors = self._library.dvdcss_readv(
self.handle, iovecs(*buffers), sectors, decrypt
)
if read_sectors < sectors:
raise exceptions.ReadError(
message_with_error(
f"Read {read_sectors} sectors, expected {sectors}",
self.error,
)
)

return read_sectors

def close(self) -> bool:
"""
Expand Down Expand Up @@ -359,6 +427,8 @@ def _load_library() -> CDLL:
lib.dvdcss_seek.restype = c_int
lib.dvdcss_read.argtypes = [c_void_p, c_char_p, c_int, c_int]
lib.dvdcss_read.restype = c_int
lib.dvdcss_readv.argtypes = [c_void_p, POINTER(Iovec), c_int, c_int]
lib.dvdcss_readv.restype = c_int
lib.dvdcss_error.argtypes = [c_void_p]
lib.dvdcss_error.restype = c_int
lib.dvdcss_is_scrambled.argtypes = [c_void_p]
Expand Down
22 changes: 21 additions & 1 deletion pydvdcss/structs.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
from ctypes import CFUNCTYPE, Structure, c_char_p, c_int, c_uint64, c_void_p
from ctypes import (
CFUNCTYPE,
Array,
Structure,
c_char,
c_char_p,
c_int,
c_uint64,
c_void_p,
cast,
)
from enum import Enum


Expand All @@ -16,6 +26,16 @@ class ReadFlag(Enum):
"""Decrypt the data it reads."""


class Iovec(Structure):
_fields_ = (("iov_base", c_void_p), ("iov_len", c_int))


def iovecs(*buffers: Array[c_char]) -> Array[Iovec]:
return (Iovec * len(buffers))(
*[Iovec(cast(buffer, c_void_p), len(buffer)) for buffer in buffers]
)


class DvdCssStreamCb(Structure):
"""
Creates a struct to match dvdcss_stream_cb.
Expand Down

0 comments on commit c2c8e19

Please sign in to comment.