Metadata-Version: 2.4
Name: riffy
Version: 0.1.2
Summary: A pure Python library for parsing and managing RIFF format files
Project-URL: Homepage, https://github.com/jmcmeen/riffy
Project-URL: Repository, https://github.com/jmcmeen/riffy
Project-URL: Issues, https://github.com/jmcmeen/riffy/issues
Author-email: John McMeen <johnmcmeen@gmail.com>
License: MIT License
        
        Copyright (c) 2025 John McMeen
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: audio,chunk,parser,riff,wav
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Provides-Extra: dev
Requires-Dist: black>=23.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# riffy

Riffy provides a pure Python implementation for working with RIFF format files, with initial support for WAV audio files. The library is designed to have **zero external dependencies** while providing robust parsing capabilities for managing RIFF chunks.

## Features

- **Zero Dependencies**: Pure Python implementation with no external dependencies
- **WAV File Support**: Complete WAV file parsing with format validation
- **RIFF Chunk Management**: Access and inspect individual RIFF chunks
- **Audio Metadata Extraction**: Extract sample rate, channels, bit depth, and duration
- **Format Validation**: Automatic validation of file format and integrity
- **Type Safety**: Full type hints for better IDE support and code quality
- **Lightweight**: Minimal footprint, perfect for embedded systems or restricted environments

## Installation

### From PyPI

```bash
pip install riffy
```

### From Source

```bash
# Clone the repository
git clone https://github.com/jmcmeen/riffy.git
cd riffy

# Install in development mode
pip install -e .

# Or build and install
pip install build
python -m build
pip install dist/riffy-*.whl
```

### For Development

```bash
# Install with development dependencies
pip install -e ".[dev]"
```

## Quick Start

### Basic WAV File Parsing

```python
from riffy import WAVParser

# Parse a WAV file (parsing happens automatically on initialization)
parser = WAVParser("audio.wav")

# Access format information
info = parser.get_info()
print(f"Sample Rate: {info['format']['sample_rate']} Hz")
print(f"Channels: {info['format']['channels']}")
print(f"Bit Depth: {info['format']['bits_per_sample']} bits")
print(f"Duration: {info['duration_seconds']:.2f} seconds")
```

### Accessing RIFF Chunks

```python
from riffy import WAVParser

# Parsing happens automatically on initialization
parser = WAVParser("audio.wav")

# Access all chunks
for chunk_id, chunk in parser.chunks.items():
    print(f"Chunk: {chunk_id}, Size: {chunk.size} bytes, Offset: {chunk.offset}")

# Access specific chunk
if 'fmt ' in parser.chunks:
    fmt_chunk = parser.chunks['fmt ']
    print(f"Format chunk size: {fmt_chunk.size}")
```

### Working with Audio Data

```python
from riffy import WAVParser

# Parsing happens automatically on initialization
parser = WAVParser("audio.wav")

# Access raw audio data
audio_data = parser.audio_data
print(f"Audio data size: {len(audio_data)} bytes")

# Get sample count
info = parser.get_info()
print(f"Total samples: {info['sample_count']}")
```

### Exporting Chunks to Binary Files

```python
from riffy import WAVParser

# Parsing happens automatically on initialization
parser = WAVParser("audio.wav")

# Export raw audio data (most common use case)
bytes_written = parser.export_audio_data("raw_audio.bin")
print(f"Exported {bytes_written} bytes of audio data")

# Export specific chunks
parser.export_chunk('fmt ', "format_chunk.bin")
parser.export_chunk('data', "data_chunk.bin")

# List all available chunks before exporting
chunks = parser.list_chunks()
for chunk_id, info in chunks.items():
    print(f"Chunk '{chunk_id}': {info['size']} bytes at offset {info['offset']}")

# Export all chunks
for chunk_id in chunks.keys():
    filename = f"{chunk_id.strip()}_chunk.bin"
    parser.export_chunk(chunk_id, filename)
```

### Getting Detailed File Information

```python
from riffy import WAVParser
import json

# Parsing happens automatically on initialization
parser = WAVParser("audio.wav")
info = parser.get_info()

# Pretty print all information
print(json.dumps(info, indent=2))
```

Output example:

```json
{
  "file_path": "/path/to/audio.wav",
  "file_size": 1234567,
  "format": {
    "audio_format": 1,
    "channels": 2,
    "sample_rate": 44100,
    "byte_rate": 176400,
    "block_align": 4,
    "bits_per_sample": 16,
    "is_pcm": true
  },
  "duration_seconds": 3.5,
  "audio_data_size": 617400,
  "sample_count": 154350,
  "chunks": {
    "fmt ": 16,
    "data": 617400
  }
}
```

## API Reference

### WAVParser

The main class for parsing WAV files.

#### Constructor

```python
WAVParser(file_path: Union[str, Path])
```

**Parameters:**

- `file_path`: Path to the WAV file (string or `pathlib.Path`)

**Note:** The file is automatically parsed during initialization.

**Raises (during initialization):**

- `FileNotFoundError`: If the file doesn't exist
- `InvalidWAVFormatError`: If the file is not a valid WAV file
- `CorruptedFileError`: If the file is corrupted or incomplete

#### Methods

##### `get_info() -> Dict`

Get comprehensive information about the parsed WAV file.

**Returns:** Dictionary with file metadata

##### `parse() -> Dict`

Re-parse the WAV file and return comprehensive information. This method can be called to refresh the parser state, but is automatically called during initialization.

**Returns:** Dictionary containing file information, format details, and chunk data

##### `export_chunk(chunk_id: str, output_path: Union[str, Path]) -> int`

Export a specific chunk's data to a binary file.

**Parameters:**

- `chunk_id`: The ID of the chunk to export (e.g., 'fmt ', 'data')
- `output_path`: Path where the chunk data will be written

**Returns:** Number of bytes written

**Raises:**

- `KeyError`: If the specified chunk doesn't exist

##### `export_audio_data(output_path: Union[str, Path]) -> int`

Export raw audio data to a binary file (convenience method).

**Parameters:**

- `output_path`: Path where the audio data will be written

**Returns:** Number of bytes written

**Raises:**

- `WAVError`: If no audio data exists

##### `list_chunks() -> Dict[str, Dict[str, int]]`

List all chunks in the WAV file with their sizes and offsets.

**Returns:** Dictionary mapping chunk IDs to their metadata (size and offset)

#### Properties

- `format_info`: `WAVFormat` object containing format details
- `chunks`: Dictionary of `WAVChunk` objects keyed by chunk ID
- `audio_data`: Raw audio data as bytes

### WAVFormat

Dataclass containing WAV format information.

**Attributes:**

- `audio_format`: Audio format code (1 = PCM)
- `channels`: Number of audio channels
- `sample_rate`: Sample rate in Hz
- `byte_rate`: Bytes per second
- `block_align`: Block alignment in bytes
- `bits_per_sample`: Bits per sample

**Properties:**

- `is_pcm`: Boolean indicating if format is PCM (uncompressed)
- `duration_seconds`: Audio duration in seconds

### WAVChunk

Dataclass representing a RIFF chunk.

**Attributes:**

- `id`: Chunk identifier (e.g., "fmt ", "data")
- `size`: Chunk size in bytes
- `data`: Raw chunk data
- `offset`: Byte offset in the file

## Exception Hierarchy

```
RiffyError (base exception)
├── WAVError
│   ├── InvalidWAVFormatError
│   ├── CorruptedFileError
│   └── UnsupportedFormatError
└── ChunkError
    ├── InvalidChunkError
    └── MissingChunkError
```

### Exception Handling

```python
from riffy import WAVParser, InvalidWAVFormatError, CorruptedFileError

try:
    parser = WAVParser("audio.wav")
    info = parser.parse()
except InvalidWAVFormatError as e:
    print(f"Invalid WAV format: {e}")
except CorruptedFileError as e:
    print(f"File is corrupted: {e}")
except FileNotFoundError:
    print("File not found")
```

## Supported Formats

Currently, Riffy supports:

- **WAV Files**: PCM (uncompressed) audio only
- **RIFF Chunks**: Standard chunk parsing for any RIFF file

### Planned Support

- AVI files
- WebP images
- Additional WAV compression formats
- RIFF chunk writing/modification

## Requirements

- Python 3.8 or higher
- No external dependencies!

## Development

### Running Tests

```bash
# Install development dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run tests with coverage
pytest --cov=riffy --cov-report=html
```

### Code Formatting

```bash
# Format code with black
black src/

# Lint with ruff
ruff check src/
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Changelog

### Version 0.1.0 (Initial Release)

- Pure Python WAV file parser
- RIFF chunk management
- Zero external dependencies
- Full type hints
- Comprehensive error handling

## References

- RIFF format specification: [Microsoft RIFF Specification](https://learn.microsoft.com/en-us/windows/win32/xaudio2/resource-interchange-file-format--riff-)
- WAV format specification: [WAV Audio File Format](http://soundfile.sapp.org/doc/WaveFormat/)

## Support

For bugs, feature requests, or questions, please [open an issue](https://github.com/jmcmeen/riffy/issues).
