Adjust structure for libdvdcss bindings.

- Add libdvdcss as a submodule.
- Add libdvdcss.h for structure definitions.
- Path to headers provided by libdvdcss.
- Add meson to build config.h for libdvdcss.
This commit is contained in:
2026-06-22 06:58:16 -04:00
parent 592bffbd3a
commit ed15a5274c
16 changed files with 227 additions and 230 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "external/libdvdcss"]
path = external/libdvdcss
url = https://code.videolan.org/videolan/libdvdcss.git
+2 -1
View File
@@ -16,7 +16,8 @@ publish = false
# Workspace member crates
#
dvd = { path = "crates/dvd" }
libdvdcss = { path = "crates/libdvdcss" }
dvdcss-sys = { path = "crates/dvdcss-sys" }
dvdcss = { path = "crates/dvdcss" }
mapping = { path = "crates/mapping" }
media = { path = "crates/media" }
plugins = { path = "crates/plugins" }
-31
View File
@@ -1,31 +0,0 @@
use std::path::PathBuf;
use media::Media;
use crate::error::Error;
pub type DVD = libdvdcss::DVD;
// Probably wipe and restart this.
// Not thought out yet.
impl TryFrom<PathBuf> for DVD {
type Error = crate::Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
libdvdcss::DVD::new(value).ok_or(Error::LibDvdCssOpenError)
}
}
impl Media for DVD {}
mod error {
use derive_more::{Display, From};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, From, Display)]
pub enum Error {
LibDvdCssOpenError,
}
}
@@ -1,13 +1,12 @@
[package]
name = "dvd"
version.workspace = true
name = "dvdcss-sys"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
repository.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
derive_more.workspace = true
libdvdcss.workspace = true
media.workspace = true
[build-dependencies]
bindgen = "0.72.1"
meson = "1.0"
+3
View File
@@ -0,0 +1,3 @@
# dvdcss-sys
A crate for pure bindings against the system libdvdcss.
@@ -1,16 +1,25 @@
use std::env;
use std::env::{self, current_dir};
use std::path::PathBuf;
fn main() {
// Path from which to search for shared libraries.
// Is there no better (automated), platform-specific way?
println!("cargo:rustc-link-search=/usr/lib");
// Locate and link the shared library.
println!("cargo:rustc-link-lib=libdvdcss");
println!("cargo:rustc-link-lib=dvdcss");
// Because clang wants an absolute path and using canonicalize() fails.
let libdir_path = current_dir()
.expect("Cannot get CWD.")
.join("../../external/libdvdcss");
// Run the meson build to produce build/config.h
meson::build(
libdir_path.to_str().unwrap(),
libdir_path.join("build").to_str().unwrap(),
);
let bindings = bindgen::Builder::default()
.header("src/wrapper.h")
.clang_arg(format!("-I{}/src", libdir_path.to_str().unwrap()))
.clang_arg(format!("-I{}/build", libdir_path.to_str().unwrap()))
// Invalidate the build if a header has changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
+2
View File
@@ -0,0 +1,2 @@
#include "dvdcss/dvdcss.h"
#include "libdvdcss.h"
@@ -1,5 +1,5 @@
[package]
name = "libdvdcss"
name = "dvdcss"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
@@ -10,6 +10,8 @@ publish.workspace = true
[dependencies]
derive_more.workspace = true
semver.workspace = true
dvdcss-sys.workspace = true
[build-dependencies]
bindgen = "0.72.1"
[features]
semver = []
encryption-type = []
+11
View File
@@ -0,0 +1,11 @@
# libdvdcss Rust Wrapper
This crate assumes a baseline version of `libdvdcss` 1.5.0.
If you wish to use the `dvdcss_get_encryption_type()` function introduced in
`libdvdcss` version 1.6.0, add the `encryption-type` feature.
For convenience, the `semver` feature can be added for a `semver::Version`
representation of `DVDCSS_VERSION` instead of a tuple.
`dvdcss_open_stream()` is not implemented. At least not yet.
+180
View File
@@ -0,0 +1,180 @@
use std::ffi::{CStr, c_int, c_void};
use std::io::{self, Error, Read, Seek, SeekFrom};
use std::path::Path;
use derive_more::From;
use dvdcss_sys::*;
pub const BLOCK_SIZE: u32 = DVDCSS_BLOCK_SIZE;
#[cfg(feature = "semver")]
pub const DVDCSS_VERSION: semver::Version = semver::Version::new(
DVDCSS_VERSION_MAJOR as u64,
DVDCSS_VERSION_MINOR as u64,
DVDCSS_VERSION_MICRO as u64,
);
#[cfg(not(feature = "semver"))]
pub const DVDCSS_VERSION: (u32, u32, u32) = (
DVDCSS_VERSION_MAJOR,
DVDCSS_VERSION_MINOR,
DVDCSS_VERSION_MICRO,
);
#[repr(u32)]
#[derive(Debug, Default)]
pub enum SeekFlag {
#[default]
None = DVDCSS_NOFLAGS,
Key = DVDCSS_SEEK_KEY,
Mpeg = DVDCSS_SEEK_MPEG,
}
// I dislike this as technically, 2 should be Unknown/Reserved and 3 as CPRM.
#[repr(i32)]
#[derive(Debug, Default, From)]
pub enum EncryptionScheme {
#[default]
None = 0,
CssCppm = 1,
//Reserved,
Cprm = 2,
Unknown,
}
pub struct Dvd {
ptr: dvdcss_t,
decrypt: bool,
}
impl Dvd {
/// Open a DVD.
/// If libdvdcss returns a null pointer, this returns None.
/// No more about the error can be discerned than this.
pub fn open<P: AsRef<Path>>(src: P) -> Option<Self> {
// Cast the path to a CStr (*const char),
// returning early if there's any issue.
let c_path = CStr::from_bytes_with_nul(src.as_ref().as_os_str().as_encoded_bytes())
.ok()?
.as_ptr();
let ptr = unsafe { dvdcss_open(c_path) };
if ptr.is_null() {
None
} else {
Some(Self {
ptr,
decrypt: false,
})
}
}
/// Create a `Dvd` from a raw pointer to `dvdcss_t`.
pub unsafe fn from_raw_pointer(ptr: dvdcss_t) -> Self {
Self {
ptr,
decrypt: false,
}
}
pub fn decrypt(&mut self, decrypt: bool) {
self.decrypt = decrypt;
}
/// Return the error message string from dvdcss_error
pub fn error(&self) -> &CStr {
unsafe { CStr::from_ptr(dvdcss_error(self.ptr)) }
}
/// Return the error message string from dvdcss_error with lossy conversion.
pub fn error_lossy(&self) -> String {
self.error().to_string_lossy().to_string()
}
/// Detect whether or not the content is scrambled.
pub fn is_scrambled(&self) -> bool {
match unsafe { dvdcss_is_scrambled(self.ptr) } {
0 => false,
1 => true,
_ => unreachable!(),
}
}
/// The length in bytes of the Dvd.
pub fn len(&self) {
self.ptr.i_fd
}
/// Return the encryption scheme in use.
#[cfg(feature = "encryption-type")]
pub fn encryption_type(&self) -> EncryptionScheme {
unsafe { dvdcss_get_encryption_type(self.ptr) }
.try_into()
.unwrap_or_default()
}
}
impl Read for Dvd {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let i_flags = if self.decrypt {
DVDCSS_READ_DECRYPT
} else {
DVDCSS_NOFLAGS
} as i32;
let retval = unsafe {
dvdcss_read(
self.ptr,
buf.as_mut_ptr() as *mut c_void,
buf.len() as i32 / BLOCK_SIZE as c_int,
i_flags,
)
};
if retval < 0 {
Err(Error::last_os_error())
} else {
Ok(retval.unsigned_abs() as usize)
}
}
}
impl Seek for Dvd {
/// Seek to some posititon in the disc.
///
/// Contrary to Rust's standard for Seek implementations,
/// this is limited to seeking in multiples of BLOCK_SIZE (2048),
/// as defined by `dvdcss_seek()`.
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
// dvdcss_seek reports in blocks, rather than bytes as expected by Rust.
//
// As for what purpose the flags serve... I haven't a clue.
let bytes = match pos {
SeekFrom::Start(p) => p as i64,
SeekFrom::Current(p) => self.stream_position()? as i64 + p,
SeekFrom::End(p) => self.stream_len()? as i64 - p,
} * BLOCK_SIZE as i64;
// Although this call generally returns a negative value on fail, as of writing
// it can only return -1, so there's no point in expanding this fail result into
// a proper enum.
let retval = unsafe { dvdcss_seek(self.ptr, bytes as c_int, SeekFlag::None as c_int) };
if retval < 0 {
Err(Error::last_os_error())
} else {
Ok((retval.unsigned_abs() * BLOCK_SIZE) as u64)
}
}
}
impl Drop for Dvd {
fn drop(&mut self) {
// Right... so drop can't handle return values.
// So... best option is ...panic?
let retval = unsafe { dvdcss_close(self.ptr) };
assert!(retval == 0);
}
}
-3
View File
@@ -1,3 +0,0 @@
# libdvdcss Rust Wrapper
Minimum `libdvdcss` version: 1.6.0
-17
View File
@@ -1,17 +0,0 @@
use derive_more::{Display, From};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, From, Display)]
pub enum Error {
#[display("I32ToEnumCastError {{ {}, {} }}", value, enum_)]
I32ToEnumCastError {
value: i32,
enum_: String,
},
InsufficientBufferLength,
ReadError {
value: i32,
},
SeekError,
}
-162
View File
@@ -1,162 +0,0 @@
use std::ffi::{CStr, c_int, c_void};
use std::path::Path;
use crate::error::{Error, Result};
mod dvdcss;
mod error;
use dvdcss::*;
use semver::Version;
pub const BLOCK_SIZE: u32 = DVDCSS_BLOCK_SIZE;
pub const DVDCSS_VERSION: semver::Version = Version::new(
DVDCSS_VERSION_MAJOR as u64,
DVDCSS_VERSION_MINOR as u64,
DVDCSS_VERSION_MICRO as u64,
);
#[repr(u32)]
#[derive(Debug, Default)]
pub enum SeekFlag {
#[default]
None = DVDCSS_NOFLAGS,
Key = DVDCSS_SEEK_KEY,
Mpeg = DVDCSS_SEEK_MPEG,
}
// I dislike this as technically, 2 should be Unknown/Reserved and 3 as CPRM.
#[repr(i32)]
#[derive(Debug, Default)]
pub enum EncryptionScheme {
#[default]
None = 0,
CssCppm = 1,
//Reserved,
Cprm = 2,
Unknown,
}
impl TryFrom<i32> for EncryptionScheme {
type Error = Error;
fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
if !(0..=2).contains(&value) {
return Err(Error::I32ToEnumCastError {
value,
enum_: "EncryptionScheme".to_string(),
});
}
Ok(match value {
0 => Self::None,
1 => Self::CssCppm,
2 => Self::Cprm,
_ => unreachable!(),
})
}
}
#[repr(transparent)]
pub struct DVD {
ptr: dvdcss_t,
}
impl DVD {
pub fn new<P: AsRef<Path>>(src: P) -> Option<Self> {
// Cast the path to a CStr (*const char), returning early if there's any issue.
let c_path = CStr::from_bytes_with_nul(src.as_ref().as_os_str().as_encoded_bytes())
.ok()?
.as_ptr();
// Make the FFI call.
let ptr = unsafe { dvdcss_open(c_path) };
if ptr.is_null() {
None
} else {
Some(Self { ptr })
}
}
// Seek in blocks (See: `BLOCK_SIZE`).
pub fn seek(&self, blocks: u32, flag: SeekFlag) -> Result<u32> {
// Although this call generally returns a negative value on fail, as of writing
// it can only return -1, so there's no point in expanding this fail result into
// a proper enum.
let retval = unsafe { dvdcss_seek(self.ptr, blocks as c_int, flag as c_int) };
if retval < 0 {
Err(Error::SeekError)
} else {
Ok(retval as u32)
}
}
pub fn read(&self, buf: &mut [u8], blocks: u32, decrypt: bool) -> Result<u32> {
// If the user has provided a wonk buffer size that's not a multiple of BLOCK_SIZE,
// I'm not fixing it for them.
// But ensure that buffer is big enough to safely read the data.
if buf.len() < (blocks * BLOCK_SIZE) as usize {
return Err(Error::InsufficientBufferLength);
}
let i_flags = if decrypt {
DVDCSS_READ_DECRYPT
} else {
DVDCSS_NOFLAGS
} as i32;
let retval = unsafe {
dvdcss_read(
self.ptr,
buf.as_mut_ptr() as *mut c_void,
blocks as c_int,
i_flags,
)
};
if retval < 0 {
// I can't track back the error return values for pf_read,
// so this is good enough for now.
return Err(Error::ReadError { value: retval });
}
Ok(retval as u32)
}
// Return the error message string from dvdcss_error
pub fn error(&self) -> &CStr {
unsafe { CStr::from_ptr(dvdcss_error(self.ptr)) }
}
// Return the error message string from dvdcss_error with lossy conversion.
pub fn error_lossy(&self) -> String {
self.error().to_string_lossy().to_string()
}
pub fn is_scrambled(&self) -> bool {
match unsafe { dvdcss_is_scrambled(self.ptr) } {
0 => false,
1 => true,
_ => unreachable!(),
}
}
/*
pub fn encryption_type(&self) -> EncryptionScheme {
unsafe { dvdcss_get_encryption_type(self.ptr) }
.try_into()
.unwrap_or_default()
}
*/
}
impl Drop for DVD {
fn drop(&mut self) {
// Right... so drop can't handle return values.
// So... best option is ...panic?
let retval = unsafe { dvdcss_close(self.ptr) };
assert!(retval == 0);
}
}
-1
View File
@@ -1 +0,0 @@
#include <dvdcss/dvdcss.h>
Vendored Submodule
+1
Submodule external/libdvdcss added at 2682a4a7ed