Skip to content

Commit

Permalink
✨ feat(src/lib.rs): Add support for search parameters in recognize me…
Browse files Browse the repository at this point in the history
…thods

📝 docs(shazamio_core.py): Update recognize methods to include search parameters

📦 build(src/params.rs): Add new module for search parameters

📝 docs(shazamio_core.pyi): Update recognize methods to include search parameters
  • Loading branch information
dotX12 committed Feb 3, 2025
1 parent bd8269b commit 1803d73
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 22 deletions.
35 changes: 31 additions & 4 deletions shazamio_core/shazamio_core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Union
from typing import Union, Optional
from os import PathLike


Expand All @@ -25,6 +25,23 @@ class Signature:
timezone: str


@dataclass(frozen=True)
class SearchParams:
"""
Search parameters for the recognize method.
**segment_duration_seconds**: The duration (in seconds) of the audio segment to analyze.
- **Default:** 10 seconds.
- **If the audio file is longer than this duration**, a centered segment of the specified duration is selected.
- Example: If the audio is **60 seconds** and `segment_duration_seconds = 10`, the extracted segment will be **from 25s to 35s**.
- **If the audio file is shorter than this duration**, the entire file is used.
- Example: If the audio is **8 seconds** and `segment_duration_seconds = 10`, the entire **8-second file** will be processed.
- **Audio is always converted to mono and down sampled to 16 kHz** before analysis.
- This parameter determines the number of samples used for frequency analysis and fingerprint generation.
"""
segment_duration_seconds: int = 10


class SignatureError(Exception):
def __init__(self, message: str):
self.message = message
Expand Down Expand Up @@ -52,31 +69,41 @@ def __init__(self, segment_duration_seconds: int = 10) -> None:
- Example: If the audio is **60 seconds** and `segment_duration_seconds = 10`, the extracted segment will be **from 25s to 35s**.
- **If the audio file is shorter than this duration**, the entire file is used.
- Example: If the audio is **8 seconds** and `segment_duration_seconds = 10`, the entire **8-second file** will be processed.
- **Audio is always converted to mono and downsampled to 16 kHz** before analysis.
- **Audio is always converted to mono and down sampled to 16 kHz** before analysis.
- This parameter determines the number of samples used for frequency analysis and fingerprint generation.
"""
self.segment_duration_seconds = segment_duration_seconds
raise NotImplemented

async def recognize_path(self, value: Union[str, PathLike]) -> Signature:
async def recognize_path(
self,
value: Union[str, PathLike],
options: Optional[SearchParams] = None,
) -> Signature:
"""
Recognize audio from a file path.
This method is a Python wrapper around a Rust implementation.
:param value: Path to an audio file.
:param options: Search parameters.
:return: Signature object.
:raises SignatureError: if an error occurs.
"""
raise NotImplemented

async def recognize_bytes(self, value: bytes) -> Signature:
async def recognize_bytes(
self,
value: bytes,
options: Optional[SearchParams] = None,
) -> Signature:
"""
Recognize audio from raw bytes.
This method is a Python wrapper around a Rust implementation.
:param value: Raw audio file as bytes.
:param options: Search parameters.
:return: Signature object.
:raises SignatureError: if an error occurs.
"""
Expand Down
35 changes: 31 additions & 4 deletions shazamio_core/shazamio_core.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Union
from typing import Union, Optional
from os import PathLike


Expand All @@ -25,6 +25,23 @@ class Signature:
timezone: str


@dataclass(frozen=True)
class SearchParams:
"""
Search parameters for the recognize method.
**segment_duration_seconds**: The duration (in seconds) of the audio segment to analyze.
- **Default:** 10 seconds.
- **If the audio file is longer than this duration**, a centered segment of the specified duration is selected.
- Example: If the audio is **60 seconds** and `segment_duration_seconds = 10`, the extracted segment will be **from 25s to 35s**.
- **If the audio file is shorter than this duration**, the entire file is used.
- Example: If the audio is **8 seconds** and `segment_duration_seconds = 10`, the entire **8-second file** will be processed.
- **Audio is always converted to mono and down sampled to 16 kHz** before analysis.
- This parameter determines the number of samples used for frequency analysis and fingerprint generation.
"""
segment_duration_seconds: int = 10


class SignatureError(Exception):
def __init__(self, message: str):
self.message = message
Expand Down Expand Up @@ -52,31 +69,41 @@ class Recognizer:
- Example: If the audio is **60 seconds** and `segment_duration_seconds = 10`, the extracted segment will be **from 25s to 35s**.
- **If the audio file is shorter than this duration**, the entire file is used.
- Example: If the audio is **8 seconds** and `segment_duration_seconds = 10`, the entire **8-second file** will be processed.
- **Audio is always converted to mono and downsampled to 16 kHz** before analysis.
- **Audio is always converted to mono and down sampled to 16 kHz** before analysis.
- This parameter determines the number of samples used for frequency analysis and fingerprint generation.
"""
self.segment_duration_seconds = segment_duration_seconds
raise NotImplemented

async def recognize_path(self, value: Union[str, PathLike]) -> Signature:
async def recognize_path(
self,
value: Union[str, PathLike],
options: Optional[SearchParams] = None,
) -> Signature:
"""
Recognize audio from a file path.
This method is a Python wrapper around a Rust implementation.
:param value: Path to an audio file.
:param options: Search parameters.
:return: Signature object.
:raises SignatureError: if an error occurs.
"""
raise NotImplemented

async def recognize_bytes(self, value: bytes) -> Signature:
async def recognize_bytes(
self,
value: bytes,
options: Optional[SearchParams] = None,
) -> Signature:
"""
Recognize audio from raw bytes.
This method is a Python wrapper around a Rust implementation.
:param value: Raw audio file as bytes.
:param options: Search parameters.
:return: Signature object.
:raises SignatureError: if an error occurs.
"""
Expand Down
57 changes: 43 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ mod errors;
mod fingerprinting;
mod response;
mod utils;
mod params;

use crate::errors::SignatureError;
use crate::response::{Geolocation, Signature, SignatureSong};
use crate::params::SearchParams;
use crate::utils::convert_signature_to_py;
use crate::utils::get_python_future;
use crate::utils::unwrap_decoded_signature;
Expand All @@ -23,6 +25,7 @@ fn shazamio_core(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<Geolocation>()?;
m.add_class::<SignatureSong>()?;
m.add_class::<Signature>()?;
m.add_class::<SearchParams>()?;

info!("shazamio_core module initialized successfully");
Ok(())
Expand All @@ -44,18 +47,31 @@ impl Recognizer {
Recognizer { segment_duration_seconds: duration }
}

fn recognize_bytes(&self, py: Python, bytes: Vec<u8>) -> PyResult<PyObject> {
debug!("Recognize bytes method called");
debug!("Segment duration: {}", self.segment_duration_seconds);
debug!("Received {} bytes for recognition", bytes.len());

let segment_duration = self.segment_duration_seconds;
fn recognize_bytes(
&self,
py: Python,
bytes: Vec<u8>,
options: Option<SearchParams>,
) -> PyResult<PyObject> {
debug!(
"recognize_bytes method called with bytes len: {} and options: {:?}",
bytes.len(),
options,
);

let search_options = options.unwrap_or_else(|| {
debug!(
"Options not provided, using default segment duration {}",
self.segment_duration_seconds,
);
SearchParams::new(Option::from(self.segment_duration_seconds))
});

let future = async move {
debug!("Starting async recognition from bytes");
let data = SignatureGenerator::make_signature_from_bytes(
bytes,
Some(segment_duration),
Some(search_options.segment_duration_seconds),
).map_err(|e| {
error!("Error in make_signature_from_bytes: {}", e);
let error_message = format!("{}", e);
Expand All @@ -72,18 +88,31 @@ impl Recognizer {
python_future.map(|any| any.to_object(py))
}

fn recognize_path(&self, py: Python, value: String) -> PyResult<PyObject> {
debug!("Recognize path method called");
debug!("Segment duration: {}", self.segment_duration_seconds);
debug!("File path: {}", value);

let segment_duration = self.segment_duration_seconds;
fn recognize_path(
&self,
py: Python,
value: String,
options: Option<SearchParams>,
) -> PyResult<PyObject> {
debug!(
"recognize_path method called with path: {} and options: {:?}",
value,
options,
);

let search_options = options.unwrap_or_else(|| {
debug!(
"Options not provided, using default segment duration {}",
self.segment_duration_seconds,
);
SearchParams::new(Option::from(self.segment_duration_seconds))
});

let future = async move {
debug!("Starting async recognition from file: {}", value);
let data = SignatureGenerator::make_signature_from_file(
&value,
Some(segment_duration),
Some(search_options.segment_duration_seconds),
).map_err(|e| {
debug!("Error in make_signature_from_file: {}", e);
let error_message = format!("{}", e);
Expand Down
18 changes: 18 additions & 0 deletions src/params.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use pyo3::{pyclass, pymethods};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[pyclass]
pub(crate) struct SearchParams {
#[pyo3(get, set)]
pub(crate) segment_duration_seconds: u32,
}
#[pymethods]
impl SearchParams {
#[new]
pub fn new(segment_duration_seconds: Option<u32>) -> Self {
SearchParams {
segment_duration_seconds: segment_duration_seconds.unwrap_or(10),
}
}
}

0 comments on commit 1803d73

Please sign in to comment.