#![cfg(feature = "python")]
use crate::UniversalEpub3Exporter;
use crate::book::Book;
use crate::kfx::writer::UniversalKfxExporter;
use crate::rag::RagChunkConfig;
use crate::section::Section;
use pyo3::exceptions::{PyKeyError, PyRuntimeError, PyValueError};
use pyo3::prelude::*;
#[pyclass(name = "Section", skip_from_py_object)]
#[derive(Clone)]
pub struct PySection {
inner: Section,
}
#[pymethods]
impl PySection {
#[getter]
pub fn index(&self) -> usize {
self.inner.index
}
#[getter]
pub fn href(&self) -> String {
self.inner.href.clone()
}
#[getter]
pub fn full_path(&self) -> String {
self.inner.full_path.clone()
}
#[getter]
pub fn raw_html(&self) -> String {
self.inner.raw_html.clone()
}
#[getter]
pub fn processed_html(&self) -> String {
self.inner.processed_html.clone()
}
#[getter]
pub fn plain_text(&self) -> String {
self.inner.plain_text.clone()
}
}
#[pyclass(name = "Book")]
pub struct PyBook {
inner: Book,
}
#[pymethods]
impl PyBook {
#[staticmethod]
pub fn open(py: Python<'_>, path: &str) -> PyResult<Self> {
let p = path.to_string();
py.detach(|| Book::from_file(&p))
.map(|book| PyBook { inner: book })
.map_err(|e| PyValueError::new_err(e.to_string()))
}
#[staticmethod]
pub fn from_bytes(py: Python<'_>, bytes: &[u8]) -> PyResult<Self> {
py.detach(|| Book::from_bytes(bytes))
.map(|book| PyBook { inner: book })
.map_err(|e| PyValueError::new_err(e.to_string()))
}
#[getter]
pub fn title(&self) -> String {
self.inner.metadata().title.clone()
}
#[getter]
pub fn authors(&self) -> Vec<String> {
self.inner.metadata().creators.clone()
}
#[getter]
pub fn languages(&self) -> Vec<String> {
self.inner.metadata().languages.clone()
}
#[getter]
pub fn description(&self) -> Option<String> {
self.inner.metadata().description.clone()
}
#[getter]
pub fn section_count(&self) -> usize {
self.inner.sections.len()
}
pub fn get_section(&mut self, index: usize) -> PyResult<PySection> {
self.inner
.get_section(index)
.map(|section| PySection { inner: section })
.map_err(|e| PyValueError::new_err(e.to_string()))
}
pub fn get_section_html(&mut self, index: usize) -> PyResult<String> {
self.inner
.get_section(index)
.map(|section| section.processed_html)
.map_err(|e| PyValueError::new_err(e.to_string()))
}
pub fn get_section_by_href(&mut self, href: &str) -> PyResult<PySection> {
let index = self
.inner
.sections
.iter()
.position(|s| s.href == href || s.full_path == href)
.ok_or_else(|| PyKeyError::new_err(format!("Section href not found: {}", href)))?;
self.get_section(index)
}
#[pyo3(signature = (max_tokens=None, overlap_tokens=None))]
pub fn to_rag_chunks_json(
&self,
py: Python<'_>,
max_tokens: Option<usize>,
overlap_tokens: Option<usize>,
) -> PyResult<String> {
let config = RagChunkConfig {
max_tokens: max_tokens.unwrap_or(512),
overlap_tokens: overlap_tokens.unwrap_or(64),
preserve_headings: true,
..Default::default()
};
let chunks = py.detach(|| self.inner.to_rag_chunks(&config));
serde_json::to_string(&chunks).map_err(|e| PyRuntimeError::new_err(e.to_string()))
}
pub fn to_webpub_manifest_json(&self) -> PyResult<String> {
serde_json::to_string(&self.inner.to_webpub_manifest())
.map_err(|e| PyRuntimeError::new_err(e.to_string()))
}
pub fn metadata_json(&self) -> PyResult<String> {
serde_json::to_string(self.inner.metadata())
.map_err(|e| PyRuntimeError::new_err(e.to_string()))
}
pub fn toc_json(&self) -> PyResult<String> {
serde_json::to_string(self.inner.toc()).map_err(|e| PyRuntimeError::new_err(e.to_string()))
}
pub fn export_epub3_bytes(&self, py: Python<'_>) -> PyResult<Vec<u8>> {
py.detach(|| UniversalEpub3Exporter::export(&self.inner))
.map_err(|e| PyRuntimeError::new_err(e.to_string()))
}
pub fn export_kfx_bytes(&self, py: Python<'_>) -> PyResult<Vec<u8>> {
py.detach(|| UniversalKfxExporter::export(&self.inner))
.map_err(|e| PyRuntimeError::new_err(e.to_string()))
}
pub fn enable_manga_mode(&mut self) {
crate::cbz::CbzBook::enable_manga_mode(&mut self.inner);
}
#[pyo3(signature = (current_index=0, window=3))]
pub fn prefetch_comic_pages(
&self,
current_index: usize,
window: usize,
) -> Vec<(usize, String, Vec<u8>)> {
crate::cbz::CbzBook::prefetch_page_images(&self.inner, current_index, window)
}
pub fn search(&self, py: Python<'_>, query: &str) -> PyResult<String> {
let q = query.to_string();
let results = py.detach(|| self.inner.search(&q));
serde_json::to_string(&results).map_err(|e| PyRuntimeError::new_err(e.to_string()))
}
}
#[pymodule]
fn ebook_rs(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PySection>()?;
m.add_class::<PyBook>()?;
Ok(())
}