579 lines
16 KiB
Python
Executable File
579 lines
16 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""
|
|
xeno-crufto
|
|
|
|
Copyright 2023, Jeff Moe <moe@spacecruft.org>
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import requests
|
|
import urllib.parse
|
|
|
|
parser = argparse.ArgumentParser(
|
|
prog='xeno-crufto',
|
|
description='Scripts for working with the xeno-canto sound archive.',
|
|
)
|
|
parser.add_argument(
|
|
"--api",
|
|
help="API URL (default https://xeno-canto.org/api/2/recordings)",
|
|
type=str,
|
|
required=False,
|
|
default="https://xeno-canto.org/api/2/recordings",
|
|
)
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
help="Print URI but don't run query (default False)",
|
|
action=argparse.BooleanOptionalAction,
|
|
type=bool,
|
|
required=False,
|
|
default=False,
|
|
)
|
|
parser.add_argument(
|
|
"--query",
|
|
help="Arbitrary query string (default None)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
|
|
|
|
"""
|
|
also: an array with the identified background species in the recording
|
|
animal-seen: was the recorded animal seen?
|
|
auto: automatic (non-supervised) recording?
|
|
bird-seen: despite the field name (which was kept to ensure backwards compatibility), this field indicates whether the recorded animal was seen
|
|
cnt: the country where the recording was made
|
|
date: the date that the recording was made
|
|
dvc: recording device used
|
|
en: the English name of the species
|
|
file-name: the original file name of the audio file
|
|
file: the URL to the audio file
|
|
gen: the generic name of the species
|
|
group: the group to which the species belongs (birds, grasshoppers, bats)
|
|
id: the catalogue number of the recording on xeno-canto
|
|
lat: the latitude of the recording in decimal coordinates
|
|
length: the length of the recording in minutes
|
|
lic: the URL describing the license of this recording
|
|
lng: the longitude of the recording in decimal coordinates
|
|
loc: the name of the locality
|
|
method: the recording method (field recording, in the hand, etc.)
|
|
mic: microphone used
|
|
osci: an object with the urls to the three versions of oscillograms
|
|
playback-used: was playback used to lure the animal?
|
|
q: the current quality rating for the recording
|
|
rec: the name of the recordist
|
|
regnr: registration number of specimen (when collected)
|
|
rmk: additional remarks by the recordist
|
|
sex: the sex of the animal
|
|
smp: sample rate
|
|
sono: an object with the urls to the four versions of sonograms
|
|
sp: the specific name (epithet) of the species
|
|
ssp: the subspecies name (subspecific epithet)
|
|
stage: the life stage of the animal (adult, juvenile, etc.)
|
|
temperature: temperature during recording (applicable to specific groups only)
|
|
time: the time of day that the recording was made
|
|
type: the sound type of the recording (combining both predefined terms such as 'call' or 'song' and additional free text options)
|
|
uploaded: the date that the recording was uploaded to xeno-canto
|
|
url: the URL specifying the details of this recording
|
|
"""
|
|
|
|
# Need to create array
|
|
# parser.add_argument(
|
|
# "--also",
|
|
# help="an array with the identified background species in the recording",
|
|
# type=str,
|
|
# required=False,
|
|
# default="",
|
|
# )
|
|
parser.add_argument(
|
|
"--animal-seen",
|
|
help="was the recorded animal seen?",
|
|
type=str,
|
|
required=False,
|
|
choices=["yes", "no", "unknown"],
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--auto",
|
|
help="(non-supervised) recording?",
|
|
type=str,
|
|
required=False,
|
|
choices=["yes", "no", "unknown"],
|
|
default="",
|
|
)
|
|
# parser.add_argument(
|
|
# "--bird-seen",
|
|
# help="despite the field name (which was kept to ensure backwards compatibility), this field indicates whether the recorded animal was seen",
|
|
# type=str,
|
|
# required=False,
|
|
# default="",
|
|
# )
|
|
parser.add_argument(
|
|
"--cnt",
|
|
help="the country where the recording was made (example: brazil)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--date",
|
|
help="the date that the recording was made (example: 2022-09-18)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--dvc",
|
|
help='recording device used (example: "Panasonic RR-US300")',
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--en",
|
|
help='the English name of the species (example: "Great Tinamou")',
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--file",
|
|
help="the URL to the audio file (example: https://xeno-canto.org/770944/download)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--file-name",
|
|
help='the original file name of the audio file (example: "XC483178-Tinamus tao_Rio Azul_1032.mp3")',
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--gen",
|
|
help='the generic name of the species (example: "Rhea")',
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--group",
|
|
help="the group to which the species belongs",
|
|
type=str,
|
|
required=False,
|
|
choices=["bats", "birds", "grasshoppers"],
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--id",
|
|
help="the catalogue number of the recording on xeno-canto (example: 830675)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--lat",
|
|
help="the latitude of the recording in decimal coordinates (example: -26.7144)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
# parser.add_argument(
|
|
# "--length",
|
|
# help="the length of the recording in minutes (example: 1:49)",
|
|
# type=str,
|
|
# required=False,
|
|
# default="",
|
|
# )
|
|
# parser.add_argument(
|
|
# "--lic",
|
|
# help="the URL describing the license of this recording (example //creativecommons.org/licenses/by-sa/4.0/)",
|
|
# type=str,
|
|
# required=False,
|
|
# default="",
|
|
# )
|
|
parser.add_argument(
|
|
"--lng",
|
|
help="the longitude of the recording in decimal coordinates (example: -67.7537)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--loc",
|
|
help='the name of the locality (example "Araponga, Minas Gerais")',
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--method",
|
|
help='the recording method (example: "field recording")',
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--mic",
|
|
help='microphone used (example: "Telinga + AT4022")',
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
# parser.add_argument(
|
|
# "--osci",
|
|
# help="an object with the urls to the three versions of oscillograms",
|
|
# type=str,
|
|
# required=False,
|
|
# default="",
|
|
# )
|
|
parser.add_argument(
|
|
"--playback-used",
|
|
help="was playback used to lure the animal?",
|
|
type=str,
|
|
required=False,
|
|
choices=["yes", "no", "unknown"],
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--q",
|
|
help='the current quality rating for the recording (Letters A to E. Prepend > or < for better or worse than. Example: ">C" for A and B.)',
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--rec",
|
|
help='the name of the recordist (example: "Jeff Moe")',
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--regnr",
|
|
help="registration number of specimen (when collected)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--rmk",
|
|
help="additional remarks by the recordist",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--sex",
|
|
help="the sex of the animal",
|
|
type=str,
|
|
required=False,
|
|
choices=["female", "male", "unknown"],
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--smp",
|
|
help="sample rate (example: 48000)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
# parser.add_argument(
|
|
# "--sono",
|
|
# help="an object with the urls to the four versions of sonograms",
|
|
# type=str,
|
|
# required=False,
|
|
# default="",
|
|
# )
|
|
parser.add_argument(
|
|
"--sp",
|
|
help="the specific name (epithet) of the species (example: obsoletus)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--ssp",
|
|
help="the subspecies name (subspecific epithet) (example: americana)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--stage",
|
|
help="the life stage of the animal",
|
|
type=str,
|
|
required=False,
|
|
choices=["adult", "juvenile", "nesting", "nymph", "subadult", "unknown"],
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--temperature",
|
|
help="temperature during recording (applicable to specific groups only)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
# parser.add_argument(
|
|
# "--time",
|
|
# help="the time of day that the recording was made (example: 17:35)",
|
|
# type=str,
|
|
# required=False,
|
|
# default="",
|
|
# )
|
|
parser.add_argument(
|
|
"--type",
|
|
help="the sound type of the recording",
|
|
type=str,
|
|
required=False,
|
|
choices=[
|
|
"aberrant",
|
|
'"alarm call"',
|
|
'"begging call"',
|
|
"call",
|
|
'"calling song"',
|
|
'"courtship song"',
|
|
'"dawn song"',
|
|
'"distress call"',
|
|
'"distrubance song"',
|
|
"drumming",
|
|
"duet",
|
|
"echolocation",
|
|
'"female song"',
|
|
'"flight call"',
|
|
'"flight song"',
|
|
"imitation",
|
|
'"nocturnal flight call"',
|
|
'"rivalry song"',
|
|
'"searching song"',
|
|
'"social call"',
|
|
"song",
|
|
"subsong",
|
|
"unknown",
|
|
],
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--uploaded",
|
|
help="the date that the recording was uploaded to xeno-canto (example: 2022-03-02)",
|
|
type=str,
|
|
required=False,
|
|
default="",
|
|
)
|
|
# parser.add_argument(
|
|
# "--url",
|
|
# help="the URL specifying the details of this recording (example: //xeno-canto.org/755078)",
|
|
# type=str,
|
|
# required=False,
|
|
# default="",
|
|
# )
|
|
|
|
|
|
args = parser.parse_args()
|
|
API_URL = args.api
|
|
DRY_RUN = args.dry_run
|
|
QUERY = args.query
|
|
|
|
# API fields
|
|
# ALSO = args.also
|
|
ANIMAL_SEEN = args.animal_seen
|
|
AUTO = args.auto
|
|
# BIRD_SEEN = args.bird_seen
|
|
CNT = args.cnt
|
|
DATE = args.date
|
|
DVC = args.dvc
|
|
EN = args.en
|
|
FILE = args.file
|
|
FILE_NAME = args.file_name
|
|
GEN = args.gen
|
|
GROUP = args.group
|
|
ID = args.id
|
|
LAT = args.lat
|
|
# LENGTH = args.length
|
|
# LIC = args.lic
|
|
LNG = args.lng
|
|
LOC = args.loc
|
|
METHOD = args.method
|
|
MIC = args.mic
|
|
# OSCI = args.osci
|
|
PLAYBACK_USED = args.playback_used
|
|
Q = args.q
|
|
REC = args.rec
|
|
REGNR = args.regnr
|
|
RMK = args.rmk
|
|
SEX = args.sex
|
|
SMP = args.smp
|
|
# SONO = args.sono
|
|
SP = args.sp
|
|
SSP = args.ssp
|
|
STAGE = args.stage
|
|
TEMPERATURE = args.temperature
|
|
# TIME = args.time
|
|
TYPE = args.type
|
|
UPLOADED = args.uploaded
|
|
# URL = args.url
|
|
|
|
|
|
def print_json(jsn):
|
|
print(json.dumps(json.loads(jsn), sort_keys=True, indent=4))
|
|
|
|
|
|
QUERYURL = ""
|
|
|
|
if QUERY:
|
|
QUERYURL = QUERYURL + "" + urllib.parse.quote('"' + QUERY + '"' + " ")
|
|
|
|
# xeno-canto API
|
|
#'''
|
|
# "also": [
|
|
# "Cercomacra cinerascens",
|
|
# "Ceratopipra rubrocapilla",
|
|
# "Cymbilaimus lineatus"
|
|
# ],
|
|
#'''
|
|
# if ALSO:
|
|
# QUERYURL = QUERYURL + "also:" + ALSO + ","
|
|
# Note, the API returns for field name "seen", but not "animal-seen"
|
|
if ANIMAL_SEEN:
|
|
QUERYURL = QUERYURL + "seen:" + urllib.parse.quote('"' + ANIMAL_SEEN + '"' + " ")
|
|
if AUTO:
|
|
QUERYURL = QUERYURL + "auto:" + urllib.parse.quote('"' + AUTO + '"' + " ")
|
|
#'''
|
|
# "bird-seen": "yes",
|
|
# The field animal-seen replaces the field bird-seen.
|
|
#'''
|
|
# if BIRD_SEEN:
|
|
# QUERYURL = QUERYURL + "bird-seen:" + BIRD_SEEN + ","
|
|
if CNT:
|
|
QUERYURL = QUERYURL + "cnt:" + urllib.parse.quote('"' + CNT + '"' + " ")
|
|
if DATE:
|
|
QUERYURL = QUERYURL + "date:" + urllib.parse.quote('"' + DATE + '"' + " ")
|
|
if DVC:
|
|
QUERYURL = QUERYURL + "dvc:" + urllib.parse.quote('"' + DVC + '"' + " ")
|
|
if EN:
|
|
QUERYURL = QUERYURL + "en:" + urllib.parse.quote('"' + EN + '"' + " ")
|
|
if FILE:
|
|
QUERYURL = QUERYURL + "file:" + urllib.parse.quote('"' + FILE + '"' + " ")
|
|
if FILE_NAME:
|
|
QUERYURL = QUERYURL + "file-name:" + urllib.parse.quote('"' + FILE_NAME + '"' + " ")
|
|
if GEN:
|
|
QUERYURL = QUERYURL + "gen:" + urllib.parse.quote('"' + GEN + '"' + " ")
|
|
if GROUP:
|
|
QUERYURL = QUERYURL + "group:" + urllib.parse.quote('"' + GROUP + '"' + " ")
|
|
if ID:
|
|
QUERYURL = QUERYURL + "id:" + urllib.parse.quote('"' + ID + '"' + " ")
|
|
if LAT:
|
|
QUERYURL = QUERYURL + "lat:" + urllib.parse.quote('"' + LAT + '"' + " ")
|
|
#'''
|
|
# "length": "1:49",
|
|
#'''
|
|
# if LENGTH:
|
|
# QUERYURL = QUERYURL + "length:" + urllib.parse.quote('"' + LENGTH + '"' + " ")
|
|
#'''
|
|
# "lic": "//creativecommons.org/licenses/by-sa/4.0/",
|
|
#'''
|
|
# https://xeno-canto.org/explore?query=lic:%22by-sa%22%20
|
|
# if LIC:
|
|
# QUERYURL = QUERYURL + "lic:" + urllib.parse.quote('"' + LIC + '"' + " ")
|
|
if LNG:
|
|
QUERYURL = QUERYURL + "lng:" + urllib.parse.quote('"' + LNG + '"' + " ")
|
|
if LOC:
|
|
QUERYURL = QUERYURL + "loc:" + urllib.parse.quote('"' + LOC + '"' + " ")
|
|
if METHOD:
|
|
QUERYURL = QUERYURL + "method:" + urllib.parse.quote('"' + METHOD + '"' + " ")
|
|
if MIC:
|
|
QUERYURL = QUERYURL + "mic:" + urllib.parse.quote('"' + MIC + '"' + " ")
|
|
#'''
|
|
# "osci": {
|
|
# "large": "//xeno-canto.org/sounds/uploaded/DGVLLRYDXS/wave/XC214239-large.png",
|
|
# "med": "//xeno-canto.org/sounds/uploaded/DGVLLRYDXS/wave/XC214239-med.png",
|
|
# "small": "//xeno-canto.org/sounds/uploaded/DGVLLRYDXS/wave/XC214239-small.png"
|
|
# },
|
|
#'''
|
|
# if OSCI:
|
|
# QUERYURL = QUERYURL + "osci:" + urllib.parse.quote('"' + OSCI + '"' + " ")
|
|
if PLAYBACK_USED:
|
|
QUERYURL = (
|
|
QUERYURL
|
|
+ "playback-used:"
|
|
+ urllib.parse.quote('"' + PLAYBACK_USED + '"' + " ")
|
|
)
|
|
if Q:
|
|
QUERYURL = QUERYURL + "q:" + urllib.parse.quote('"' + Q + '"' + " ")
|
|
if REC:
|
|
QUERYURL = QUERYURL + "rec:" + urllib.parse.quote('"' + REC + '"' + " ")
|
|
if REGNR:
|
|
QUERYURL = QUERYURL + "regnr:" + urllib.parse.quote('"' + REGNR + '"' + " ")
|
|
if RMK:
|
|
QUERYURL = QUERYURL + "rmk:" + urllib.parse.quote('"' + RMK + '"' + " ")
|
|
if SEX:
|
|
QUERYURL = QUERYURL + "sex:" + urllib.parse.quote('"' + SEX + '"' + " ")
|
|
if SMP:
|
|
QUERYURL = QUERYURL + "smp:" + urllib.parse.quote('"' + SMP + '"' + " ")
|
|
#'''
|
|
# "sono": {
|
|
# "full": "//xeno-canto.org/sounds/uploaded/TGBFXDVERJ/ffts/XC171181-full.png",
|
|
# "large": "//xeno-canto.org/sounds/uploaded/TGBFXDVERJ/ffts/XC171181-large.png",
|
|
# "med": "//xeno-canto.org/sounds/uploaded/TGBFXDVERJ/ffts/XC171181-med.png",
|
|
# "small": "//xeno-canto.org/sounds/uploaded/TGBFXDVERJ/ffts/XC171181-small.png"
|
|
# },
|
|
#'''
|
|
# if SONO:
|
|
# QUERYURL = QUERYURL + "sono:" + urllib.parse.quote('"' + SONO + '"' + " ")
|
|
if SP:
|
|
QUERYURL = QUERYURL + "sp:" + urllib.parse.quote('"' + SP + '"' + " ")
|
|
if SSP:
|
|
QUERYURL = QUERYURL + "ssp:" + urllib.parse.quote('"' + SSP + '"' + " ")
|
|
if STAGE:
|
|
QUERYURL = QUERYURL + "stage:" + urllib.parse.quote('"' + STAGE + '"' + " ")
|
|
if TEMPERATURE:
|
|
QUERYURL = (
|
|
QUERYURL + "temperature:" + urllib.parse.quote('"' + TEMPERATURE + '"' + " ")
|
|
)
|
|
#'''
|
|
# "time": "17:35",
|
|
#'''
|
|
# if TIME:
|
|
# QUERYURL = QUERYURL + "time:" + urllib.parse.quote('"' + TIME + '"' + " ")
|
|
if TYPE:
|
|
QUERYURL = QUERYURL + "type:" + urllib.parse.quote('"' + TYPE + '"' + " ")
|
|
if UPLOADED:
|
|
QUERYURL = QUERYURL + "uploaded:" + urllib.parse.quote('"' + UPLOADED + '"' + " ")
|
|
#'''
|
|
# "url": "//xeno-canto.org/755078"
|
|
#'''
|
|
# if URL:
|
|
# QUERYURL = QUERYURL + "url:" + URL + ","
|
|
# QUERYURL = QUERYURL + "url:" + urllib.parse.quote('"' + URL + '"' + " ")
|
|
|
|
QUERYURL = QUERYURL.rstrip(f"20")
|
|
QUERYURL = QUERYURL.rstrip(f"%%")
|
|
|
|
if QUERYURL == "":
|
|
print("See xeno-crufto -h for help")
|
|
exit()
|
|
|
|
QUERYURL = "?query=" + QUERYURL
|
|
QUERYURL = API_URL + QUERYURL
|
|
|
|
if DRY_RUN == True:
|
|
print(QUERYURL)
|
|
else:
|
|
resp = requests.post(f"{QUERYURL}")
|
|
print_json(resp.text)
|