Skip to content

Commit

Permalink
Implement RecordingFileObject class (#82)
Browse files Browse the repository at this point in the history
* Implement RecordingFileObject class

* Fix minor formatting

* Add documentation for new Recording classes
  • Loading branch information
joeweiss authored Aug 26, 2023
1 parent 74c146f commit 2452913
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 6 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ print(recording.detections)
'start_time': 12.0}]
```

The `Recording` class takes a file path as an argument. You can also use `RecordingFileObject` to analyze an in-memory object, or `RecordingBuffer` for handling an array buffer.

### Using a custom classifier with BirdNET-Analyzer

To use a [model trained with BirdNET-Analyzer](https://github.com/kahst/BirdNET-Analyzer#training), pass your labels and model path to the `Analyzer` class.
Expand Down
23 changes: 23 additions & 0 deletions examples/analyze_from_url.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import requests
import io

from datetime import datetime
from pprint import pprint
from birdnetlib import RecordingFileObject
from birdnetlib.analyzer import Analyzer

# Mississippi Kite from Xeno-Canto.
r = requests.get("https://xeno-canto.org/669899/download")
analyzer = Analyzer()

with io.BytesIO(r.content) as fileObj:
recording = RecordingFileObject(
analyzer,
fileObj,
lat=35.6,
lon=-77.3,
date=datetime(year=2023, month=6, day=27), # use date or week_48
min_conf=0.25,
)
recording.analyze()
pprint(recording.detections)
13 changes: 8 additions & 5 deletions examples/simple_tcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
If you want to stream from somewhere other than the localhost,
change the TCPServer address from 127.0.0.1 to 0.0.0.0
"""


class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
analyzer = Analyzer()
#Read WAV data from the socket
for rate,data in wavutils.bufferwavs(self.rfile):
#Make a RecordingBuffer with buffer and rate
# Read WAV data from the socket
for rate, data in wavutils.bufferwavs(self.rfile):
# Make a RecordingBuffer with buffer and rate
recording = RecordingBuffer(
analyzer,
data,
Expand All @@ -41,9 +43,10 @@ def handle(self):
recording.analyze()
pprint(recording.detections)

if __name__=="__main__":

if __name__ == "__main__":
try:
with socketserver.TCPServer(('127.0.0.1', 9988), MyTCPHandler) as server:
with socketserver.TCPServer(("127.0.0.1", 9988), MyTCPHandler) as server:
print("Birdnetlib forever!")
server.serve_forever()
except KeyboardInterrupt:
Expand Down
8 changes: 7 additions & 1 deletion src/birdnetlib/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
from birdnetlib.main import Recording, Detection, MultiProcessRecording, RecordingBuffer
from birdnetlib.main import (
Recording,
Detection,
MultiProcessRecording,
RecordingBuffer,
RecordingFileObject,
)
43 changes: 43 additions & 0 deletions src/birdnetlib/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,49 @@ def read_audio_data(self):
self.process_audio_data(self.rate)


class RecordingFileObject(RecordingBase):
def __init__(
self,
analyzer,
file_obj,
week_48=-1,
date=None,
sensitivity=1.0,
lat=None,
lon=None,
min_conf=0.1,
overlap=0.0,
):
self.file_obj = file_obj
super().__init__(
analyzer, week_48, date, sensitivity, lat, lon, min_conf, overlap
)

@property
def filename(self):
return "File Object"

def read_audio_data(self):
print("read_audio_data")
# Open file with librosa
try:
self.ndarray, rate = librosa.load(
self.file_obj, sr=SAMPLE_RATE, mono=True, res_type="kaiser_fast"
)
self.duration = librosa.get_duration(y=self.ndarray, sr=SAMPLE_RATE)
except audioread.exceptions.NoBackendError as e:
print(e)
raise AudioFormatError("Audio format could not be opened.")
except FileNotFoundError as e:
print(e)
raise e
except BaseException as e:
print(e)
raise AudioFormatError("Generic audio read error occurred from librosa.")

self.process_audio_data(rate)


class MultiProcessRecording:

"""Stub class for multiprocessing recording results."""
Expand Down
50 changes: 50 additions & 0 deletions tests/test_fileobject_analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from birdnetlib import Recording, RecordingFileObject
from birdnetlib.analyzer import Analyzer
import io
from pprint import pprint
import os


def test_without_species_list():
# Process file with command line utility, then process with python library and ensure equal commandline_results.

lon = -120.7463
lat = 35.4244
week_48 = 18
min_conf = 0.25
input_path = os.path.join(os.path.dirname(__file__), "test_files/soundscape.wav")

analyzer = Analyzer()

# Analyzer with file path.

recording_from_path = Recording(
analyzer,
input_path,
lat=lat,
lon=lon,
week_48=week_48,
min_conf=min_conf,
)
recording_from_path.analyze()
pprint(recording_from_path.detections)

# Analyze with file object.
with open(input_path, "rb") as file:
binary_data = file.read()

with io.BytesIO(binary_data) as fileObj:
recording_from_file_obj = RecordingFileObject(
analyzer,
fileObj,
lat=lat,
lon=lon,
week_48=week_48,
min_conf=min_conf,
)
recording_from_file_obj.analyze()
pprint(recording_from_file_obj.detections)

# Check that detections match.
assert len(recording_from_path.detections) > 0
assert recording_from_path.detections == recording_from_file_obj.detections

0 comments on commit 2452913

Please sign in to comment.