# rivwebpyapi.py # # Python3 binding for the Rivendell Web API # # (C) Copyright 2021 Fred Gleason # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # import sys import datetime from datetime import timedelta import requests from urllib.parse import urlparse from xml.sax.handler import ContentHandler import xml.sax # # Audio Formats # AUDIO_FORMAT_PCM16_WAV=0 AUDIO_FORMAT_PCM24_WAV=7 AUDIO_FORMAT_MPEG_L2_WAV=6 AUDIO_FORMAT_MPEG_L2_NATIVE=2 AUDIO_FORMAT_MPEG_L3_NATIVE=3 AUDIO_FORMAT_FLAC=4 AUDIO_FORMAT_OGG_VORBIS=5 class RivWebPyApi_ListHandler(ContentHandler): def __init__(self,base_tag,fields): self.__output=[] self.__record={} self.__base_tag=base_tag self.__fields=fields; self.__field='' def output(self): for d in self.__output: # Convert to the appropriate types for f in self.__fields.keys(): if(d[f]!=None): d[f]=d[f].strip() if(self.__fields[f]=='boolean'): d[f]=self.__fromBoolean(d[f]) elif(self.__fields[f]=='datetime'): d[f]=self.__fromDatetime(d[f]) elif(self.__fields[f]=='date'): d[f]=self.__fromDate(d[f]) elif(self.__fields[f]=='integer'): d[f]=self.__fromInteger(d[f]) elif(self.__fields[f]=='time'): d[f]=self.__fromTime(d[f]) return self.__output def startElement(self,tag,attrs): if(tag==self.__base_tag): # Create new (empty) record self.__record={} for f in self.__fields.keys(): self.__record[f]=None else: if(tag in self.__fields.keys()): self.__record[tag]='' if('src' in attrs.keys()): tag=tag+attrs['src'].capitalize() self.__record[tag]='' self.__field=tag def endElement(self,tag): if(tag==self.__base_tag): # Add completed record to output self.__output.append(self.__record) self.__field='' def characters(self,content): if(self.__field in self.__fields.keys()): # Add content to field self.__record[self.__field]=self.__record[self.__field]+content def __fromBoolean(self,str): if(not str): return None if((str=='1')or(str.lower()=='true')): return True if((str=='0')or(str.lower()=='false')): return False raise ValueError('invalid boolean value') def __fromDate(self,str): if(not str): return None f0=str.split('-') if(len(f0)!=3): raise(ValueError('invalid date string')) return datetime.date(year=int(f0[0]), month=int(f0[1]), day=int(f0[2])); def __fromDatetime(self,str): if(not str): return None f0=str.split('T') if(len(f0)!=2): raise ValueError('invalid datetime string') date_parts=f0[0].split('-') if(len(date_parts)!=3): raise ValueError('invalid datetime string') time_parts=f0[1][0:7].split(':') if(len(time_parts)!=3): raise ValueError('invalid datetime string') offset_minutes=60*int(f0[1][9:11])+int(f0[1][12:14]) offset=timedelta(minutes=offset_minutes) if(f0[1][8:9]=='-'): offset=-offset return datetime.datetime(year=int(date_parts[0]), month=int(date_parts[1]), day=int(date_parts[2]), hour=int(time_parts[0]), minute=int(time_parts[1]), second=int(time_parts[2]), tzinfo=datetime.timezone(offset)); def __fromInteger(self,str): if(not str): return None return int(str) def __fromTime(self,str): if(not str): return None # # Split time from timezone fields # timestr='' zonestr='' if('+' in str): f0=str.split('+') timestr=f0[0] zonestr=f0[1] elif('-' in str): f0=str.split('-') timestr=f0[0] zonestr=f0[1] else: timestr=str # # Calculate time part # time_parts=timestr.split(':') if(len(time_parts)!=3): raise ValueError('invalid time string') msecs=0 if('.' in time_parts[2]): f0=time_parts[2].split('.') msecs=int(f0[1]) time_parts[2]=f0[0] # # Calculate timezone offset # offset_minutes=0 if(zonestr): offset_minutes=60*int(zonestr[0:2])+int(timestr[3:5]) # # Put it all together # if(zonestr): offset=timedelta(minutes=offset_minutes) if('-' in str): offset=-offset return datetime.time(hour=int(time_parts[0]), minute=int(time_parts[1]), second=int(time_parts[2]), microsecond=1000*msecs, tzinfo=datetime.timezone(offset)) return datetime.time(hour=int(time_parts[0]), minute=int(time_parts[1]), second=int(time_parts[2]), microsecond=1000*msecs) class rivwebpyapi(object): """ Create a 'RivWebPyApi' object for accessing the Web API. """ def __init__(self,url,username,password): """ Initialize a WebAPI connection to a Rivendell instance. Takes three arguments: url - The URL of the service. Typically of the form 'http://rivendell.example.com' (string). username - The Rivendell Username to connect as (string). password - The password for the specified Rivendell Username (string). """ # # Normalize the URL # parsed_url=urlparse(url) parsed_url=parsed_url._replace(path='/rd-bin/rdxport.cgi', params='', query='', fragment='') self.__connection_url=parsed_url.geturl() self.__connection_username=username self.__connection_password=password def AddCart(self,group_name,cart_type,cart_number=0): """ Add a new cart to the cart library. Returns the metadata of the newly created cart (list of dictionaries). Takes the following arguments: group_name - The name of the group to which the created cart will belong (string). cart_type - The type of cart to be created. Valid values are 'audio' and 'macro' (string). cart_number - The number of the desired cart, in the range 1 - 999999. Optional. If no cart number is specified, the next available cart number for the specified group will be used (integer). """ if((cart_number<0)or(cart_number>999999)): raise ValueError('invalid cart number') if((cart_type!='audio')and(cart_type!='macro')): raise ValueError('invalid cart type') # # Build the WebAPI arguments # postdata={ 'COMMAND': '12', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'GROUP_NAME': str(group_name), 'TYPE': str(cart_type) } if(cart_number>0): postdata['CART_NUMBER']=str(cart_number) print('ARGS: '+str(postdata)) # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'number': 'integer', 'type': 'string', 'groupName': 'string', 'title': 'string', 'artist': 'string', 'album': 'string', 'year': 'integer', 'label': 'string', 'client': 'string', 'agency': 'string', 'publisher': 'string', 'composer': 'string', 'conductor': 'string', 'userDefined': 'string', 'usageCode': 'integer', 'forcedLength': 'string', 'averageLength': 'string', 'lengthDeviation': 'string', 'averageSegueLength': 'string', 'averageHookLength': 'string', 'minimumTalkLength': 'string', 'maximumTalkLength': 'string', 'cutQuantity': 'integer', 'lastCutPlayed': 'integer', 'enforceLength': 'boolean', 'asyncronous': 'boolean', 'owner': 'string', 'metadataDatetime': 'datetime', 'songId': 'string' } handler=RivWebPyApi_ListHandler(base_tag='cart',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def AddCut(self,cart_number): """ Add a new cut to an existing audio cart. Returns the metadata of the newly created cut (list of dictionaries). Takes the following argument: cart_number - The number of the desired cart, in the range 1 - 999999 (integer) """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') # # Build the WebAPI arguments # postdata={ 'COMMAND': '10', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number) } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'cutName': 'string', 'cartNumber': 'integer', 'cutNumber': 'integer', 'evergreen': 'boolean', 'description': 'string', 'outcue': 'string', 'isrc': 'string', 'isci': 'string', 'recordingMbId': 'string', 'releaseMbId': 'string', 'length': 'integer', 'originDatetime': 'datetime', 'startDatetime': 'datetime', 'endDatetime': 'datetime', 'sun': 'boolean', 'mon': 'boolean', 'tue': 'boolean', 'wed': 'boolean', 'thu': 'boolean', 'fri': 'boolean', 'sat': 'boolean', 'startDaypart': 'time', 'endDaypart': 'time', 'originName': 'string', 'originLoginName': 'string', 'sourceHostname': 'string', 'weight': 'integer', 'lastPlayDatetime': 'datetime', 'playCounter': 'integer', 'codingFormat': 'integer', 'sampleRate': 'integer', 'bitRate': 'integer', 'channels': 'integer', 'playGain': 'integer', 'startPoint': 'integer', 'endPoint': 'integer', 'fadeupPoint': 'integer', 'fadedownPoint': 'integer', 'segueStartPoint': 'integer', 'segueEndPoint': 'integer', 'segueGain': 'integer', 'hookStartPoint': 'integer', 'hookEndPoint': 'integer', 'talkStartPoint': 'integer', 'talkEndPoint': 'integer' } handler=RivWebPyApi_ListHandler(base_tag='cut',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def AddLog(self,service_name,log_name): """ Create a new log. Takes the following arguments: service-name - The name of the service to which the new log will belong (string). log-name - The name of the log to be created (string). """ if(not service_name): raise ValueError('invalid service name') if(not log_name): raise ValueError('invalid log name') # # Build the WebAPI arguments # postdata={ 'COMMAND': '29', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'SERVICE_NAME': str(service_name), 'LOG_NAME': str(log_name) } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() def AssignSchedCode(self,cart_number,sched_code): """ Assign a schedule code to a cart. Takes the following arguments: cart_number - The number of the desired cart, in the range 1 - 999999 (integer) sched_code - An existing schedule code (integer). """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') if(not sched_code): raise ValueError('invalid schedule code') # # Build the WebAPI arguments # postdata={ 'COMMAND': '25', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number), 'CODE': sched_code } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() def AudioInfo(self,cart_number,cut_number): """ Get information about an entry in the Rivendell audio store (list of dictionaries). cart_number - The number of the desired cart, in the range 1 - 999999 (integer) cut_number - The number of the desired cut, in the range 1 - 999 (integer) """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') if((cut_number<1)or(cut_number>999)): raise ValueError('invalid cut number') # # Build the WebAPI arguments # postdata={ 'COMMAND': '19', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number), 'CUT_NUMBER': str(cut_number) } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'cartNumber': 'integer', 'cutNumber': 'integer', 'format': 'integer', 'channels': 'integer', 'sampleRate': 'integer', 'bitRate': 'integer', 'frames': 'integer', 'length': 'integer' } handler=RivWebPyApi_ListHandler(base_tag='audioInfo',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def AudioStore(self): """ Get information about the audio store (list of dictionaries). """ # # Build the WebAPI arguments # postdata={ 'COMMAND': '23', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'freeBytes': 'integer', 'totalBytes': 'integer' } handler=RivWebPyApi_ListHandler(base_tag='audioStore',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def DeleteAudio(self,cart_number,cut_number): """ Delete PCM/MPEG data from the audio store. cart_number - The number of the desired cart, in the range 1 - 999999 (integer) cut_number - The number of the desired cut, in the range 1 - 999 (integer) """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') if((cut_number<1)or(cut_number>999)): raise ValueError('invalid cut number') # # Build the WebAPI arguments # postdata={ 'COMMAND': '3', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number), 'CUT_NUMBER': str(cut_number) } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'cartNumber': 'integer', 'cutNumber': 'integer', 'format': 'integer', 'channels': 'integer', 'sampleRate': 'integer', 'bitRate': 'integer', 'frames': 'integer', 'length': 'integer' } handler=RivWebPyApi_ListHandler(base_tag='audioInfo',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def DeleteLog(self,log_name): """ Delete an existing log. Takes the following argument: log_name - The name of the log to be deleted (string). """ if(not log_name): raise ValueError('invalid log name') # # Build the WebAPI arguments # postdata={ 'COMMAND': '30', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'LOG_NAME': log_name } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() def Export(self,filename,cart_number,cut_number,audio_format,channels, sample_rate,bit_rate,quality,start_point,end_point, normalization_level,enable_metadata): """ Export audio data from the audio store (file). Takes the following arguments: filename - Location to save the exported audio file (string). cart_number - The number of the desired cart, in the range 1 - 999999 (integer) cut_number - The number of the desired cut, in the range 1 - 999 (integer) audio_format - Format of the exported audio (AUDIO_FORMAT) channels - Channel count of the exported audio (integer) sample_rate - Sample rate of the exported audio (integer, samples/sec) bit_rate - Bit rate of the export audio [MPEG formats only] (integer, bits/sec) quality - Quality [OggVorbis only] (integer, -1 - 10) start_point - Start point of audio export (integer, mS from absolute start of stored audio) end_point - End point of audio export (integer, mS from absolute start of stored audio) normalization_level - Normalization level, 0 = no normalization (integer, dBFS) enable_metadata - Encode metadata into exported file (boolean) """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') if((cut_number<1)or(cut_number>999)): raise ValueError('invalid cut number') # # Build the WebAPI arguments # postdata={ 'COMMAND': '1', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number), 'CUT_NUMBER': str(cut_number), 'FORMAT': str(audio_format), 'CHANNELS': str(channels), 'SAMPLE_RATE': str(sample_rate), 'BIT_RATE': str(bit_rate), 'QUALITY': str(quality), 'START_POINT': str(start_point), 'END_POINT': str(end_point), 'NORMALIZATION_LEVEL': str(normalization_level) } if(enable_metadata): postdata['ENABLE_METADATA']='1' else: postdata['ENABLE_METADATA']='0' # # Fetch the audio data # r=requests.post(self.__connection_url,data=postdata,stream=True) if(r.status_code!=requests.codes.ok): r.raise_for_status() with open(filename,'wb') as handle: for block in r.iter_content(1024): handle.write(block) def ExportPeaks(self,cart_number,cut_number): """ Export audio peak data from the audio store (bytes). Takes the following arguments: cart_number - The number of the desired cart, in the range 1 - 999999 (integer) cut_number - The number of the desired cut, in the range 1 - 999 (integer) """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') if((cut_number<1)or(cut_number>999)): raise ValueError('invalid cut number') # # Build the WebAPI arguments # postdata={ 'COMMAND': '16', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number), 'CUT_NUMBER': str(cut_number) } # # Fetch the peak data # r=requests.post(self.__connection_url,data=postdata,stream=True) if(r.status_code!=requests.codes.ok): r.raise_for_status() ret=bytes() for block in r.iter_content(chunk_size=1024): ret=ret+block return ret def ListCart(self,cart_number,include_cuts=False): """ Returns the metadata associated with a Rivendell cart (list of dictionaries). Takes the following arguments: cart_number - The number of the desired cart, in the range 1 - 999999 (integer) include_cuts - Include cut information in the return. Default is False. (boolean) """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') # # Build the WebAPI arguments # postdata={ 'COMMAND': '7', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number), 'INCLUDE_CUTS': '0' } if(include_cuts): postdata['INCLUDE_CUTS']='1' # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'number': 'integer', 'type': 'string', 'groupName': 'string', 'title': 'string', 'artist': 'string', 'album': 'string', 'year': 'integer', 'label': 'string', 'client': 'string', 'agency': 'string', 'publisher': 'string', 'composer': 'string', 'conductor': 'string', 'userDefined': 'string', 'usageCode': 'integer', 'forcedLength': 'string', 'averageLength': 'string', 'lengthDeviation': 'string', 'averageSegueLength': 'string', 'averageHookLength': 'string', 'minimumTalkLength': 'string', 'maximumTalkLength': 'string', 'cutQuantity': 'integer', 'lastCutPlayed': 'integer', 'enforceLength': 'boolean', 'asyncronous': 'boolean', 'owner': 'string', 'metadataDatetime': 'datetime', 'songId': 'string' } handler=RivWebPyApi_ListHandler(base_tag='cart',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def ListCarts(self,group_name='',filter_string='',cart_type='all', include_cuts=False): """ Returns the metadata associated with a set of Rivendell carts (list of dictionaries) Takes the following arguments: group_name - Filter returns to only include carts belonging to the specified group. Default is to not filter by group. (string) filter_string - Filter returns to only include carts matching the filter string. Default is not to filter by phrase. (string) cart_type - Filter returns to only include carts matching the specified type. Recognized values are 'all', 'audio' or 'macro'. Default is 'all'. (string) include_cuts - Include cut information in the return. Default is False. (boolean) """ # # Build the WebAPI arguments # postdata={ 'COMMAND': '6', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'GROUP_NAME': str(group_name), 'FILTER': filter_string, 'TYPE': cart_type.lower(), 'INCLUDE_CUTS': '0' } if(include_cuts): postdata['INCLUDE_CUTS']='1' # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'number': 'integer', 'type': 'string', 'groupName': 'string', 'title': 'string', 'artist': 'string', 'album': 'string', 'year': 'integer', 'label': 'string', 'client': 'string', 'agency': 'string', 'publisher': 'string', 'composer': 'string', 'conductor': 'string', 'userDefined': 'string', 'usageCode': 'integer', 'forcedLength': 'string', 'averageLength': 'string', 'lengthDeviation': 'string', 'averageSegueLength': 'string', 'averageHookLength': 'string', 'minimumTalkLength': 'string', 'maximumTalkLength': 'string', 'cutQuantity': 'integer', 'lastCutPlayed': 'integer', 'enforceLength': 'boolean', 'asyncronous': 'boolean', 'owner': 'string', 'metadataDatetime': 'datetime', 'songId': 'string' } handler=RivWebPyApi_ListHandler(base_tag='cart',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def ListCartSchedCodes(self,cart_number): """ Returns the set of scheduler codes associated with a Rivendell cart (list of dictionaries) Takes the following arguments: cart_number - The number of the cart for the desired schedule codes, in the range 1 - 999999 (integer) """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') # # Build the WebAPI arguments # postdata={ 'COMMAND': '27', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number) } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'code': 'string', 'description': 'string' } handler=RivWebPyApi_ListHandler(base_tag='schedCode',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def ListCut(self,cart_number,cut_number): """ Returns the metadata associated with a Rivendell cut (list of dictionaries). Takes the following arguments: cart_number - The number of the desired cart, in the range 1 - 999999 (integer) cut_number - The number of the desired cut, in the range 1 - 999 (integer) """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') if((cut_number<1)or(cut_number>999)): raise ValueError('invalid cut number') # # Build the WebAPI arguments # postdata={ 'COMMAND': '8', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number), 'CUT_NUMBER': str(cut_number) } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'cutName': 'string', 'cartNumber': 'integer', 'cutNumber': 'integer', 'evergreen': 'boolean', 'description': 'string', 'outcue': 'string', 'isrc': 'string', 'isci': 'string', 'recordingMbId': 'string', 'releaseMbId': 'string', 'length': 'integer', 'originDatetime': 'datetime', 'startDatetime': 'datetime', 'endDatetime': 'datetime', 'sun': 'boolean', 'mon': 'boolean', 'tue': 'boolean', 'wed': 'boolean', 'thu': 'boolean', 'fri': 'boolean', 'sat': 'boolean', 'startDaypart': 'time', 'endDaypart': 'time', 'originName': 'string', 'originLoginName': 'string', 'sourceHostname': 'string', 'weight': 'integer', 'lastPlayDatetime': 'datetime', 'playCounter': 'integer', 'codingFormat': 'integer', 'sampleRate': 'integer', 'bitRate': 'integer', 'channels': 'integer', 'playGain': 'integer', 'startPoint': 'integer', 'endPoint': 'integer', 'fadeupPoint': 'integer', 'fadedownPoint': 'integer', 'segueStartPoint': 'integer', 'segueEndPoint': 'integer', 'segueGain': 'integer', 'hookStartPoint': 'integer', 'hookEndPoint': 'integer', 'talkStartPoint': 'integer', 'talkEndPoint': 'integer' } handler=RivWebPyApi_ListHandler(base_tag='cut',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def ListCuts(self,cart_number): """ Returns the metadata associated with all of the cuts in a Rivendell cart (list of dictionaries). Takes the following argument: cart_number - The number of the desired cart, in the range 1 - 999999 (integer) """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') # # Build the WebAPI arguments # postdata={ 'COMMAND': '9', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number) } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'cutName': 'string', 'cartNumber': 'integer', 'cutNumber': 'integer', 'evergreen': 'boolean', 'description': 'string', 'outcue': 'string', 'isrc': 'string', 'isci': 'string', 'recordingMbId': 'string', 'releaseMbId': 'string', 'length': 'integer', 'originDatetime': 'datetime', 'startDatetime': 'datetime', 'endDatetime': 'datetime', 'sun': 'boolean', 'mon': 'boolean', 'tue': 'boolean', 'wed': 'boolean', 'thu': 'boolean', 'fri': 'boolean', 'sat': 'boolean', 'startDaypart': 'time', 'endDaypart': 'time', 'originName': 'string', 'originLoginName': 'string', 'sourceHostname': 'string', 'weight': 'integer', 'lastPlayDatetime': 'datetime', 'playCounter': 'integer', 'codingFormat': 'integer', 'sampleRate': 'integer', 'bitRate': 'integer', 'channels': 'integer', 'playGain': 'integer', 'startPoint': 'integer', 'endPoint': 'integer', 'fadeupPoint': 'integer', 'fadedownPoint': 'integer', 'segueStartPoint': 'integer', 'segueEndPoint': 'integer', 'segueGain': 'integer', 'hookStartPoint': 'integer', 'hookEndPoint': 'integer', 'talkStartPoint': 'integer', 'talkEndPoint': 'integer' } handler=RivWebPyApi_ListHandler(base_tag='cut',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def ListGroup(self,group_name): """ Returns a Rivendell group (list of dictionaries). Takes the following argument: group_name - Return the attributes of the specified group name. (string) """ # # Build the WebAPI arguments # postdata={ 'COMMAND': '5', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'GROUP_NAME': group_name } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'name': 'string', 'description': 'string', 'defaultCartType': 'string', 'defaultLowCart': 'integer', 'defaultHighCart': 'integer', 'cutShelfLife': 'integer', 'defaultTitle': 'string', 'enforceCartRange': 'boolean', 'reportTfc': 'boolean', 'reportMus': 'boolean', 'color': 'string' } handler=RivWebPyApi_ListHandler(base_tag='group',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def ListGroups(self): """ Returns a list of all Rivendell groups (list of dictionaries). """ # # Build the WebAPI arguments # postdata={ 'COMMAND': '4', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'name': 'string', 'description': 'string', 'defaultCartType': 'string', 'defaultLowCart': 'integer', 'defaultHighCart': 'integer', 'cutShelfLife': 'integer', 'defaultTitle': 'string', 'enforceCartRange': 'boolean', 'reportTfc': 'boolean', 'reportMus': 'boolean', 'color': 'string' } handler=RivWebPyApi_ListHandler(base_tag='group',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def ListLog(self,log_name): """ Returns the contents of a Rivendell log (list of dictionaries). Takes the following argument: log_name - Return the specified log. (string) """ # # Build the WebAPI arguments # postdata={ 'COMMAND': '22', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'NAME': log_name } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'line': 'integer', 'id': 'integer', 'type': 'string', 'cartType': 'string', 'cartNumber': 'integer', 'cutNumber': 'integer', 'groupName': 'string', 'groupColor': 'string', 'title': 'string', 'artist': 'string', 'publisher': 'string', 'composer': 'string', 'album': 'string', 'label': 'string', 'year': 'integer', 'client': 'string', 'agency': 'string', 'conductor': 'string', 'userDefined': 'string', 'usageCode': 'integer', 'enforceLength': 'boolean', 'forcedLength': 'string', 'evergreen': 'boolean', 'source': 'string', 'timeType': 'string', 'startTime': 'time', 'transitionType': 'string', 'cutQuantity': 'integer', 'lastCutPlayed': 'integer', 'markerComment': 'string', 'markerLabel': 'string', 'description': 'string', 'isrc': 'string', 'isci': 'string', 'recordingMbId': 'string', 'releaseMbId': 'string', 'originUser': 'string', 'originDateTime': 'datetime', 'startPointCart': 'integer', 'startPointLog': 'integer', 'endPointCart': 'integer', 'endPointLog': 'integer', 'segueStartPointCart': 'integer', 'segueStartPointLog': 'integer', 'segueEndPointCart': 'integer', 'segueEndPointLog': 'integer', 'segueGain': 'integer', 'fadeupPointCart': 'integer', 'fadeupPointLog': 'integer', 'fadeupGain': 'integer', 'fadedownPointCart': 'integer', 'fadedownPointLog': 'integer', 'fadedownGain': 'integer', 'duckUpGain': 'integer', 'duckDownGain': 'integer', 'talkStartPoint': 'integer', 'talkEndPoint': 'integer', 'hookMode': 'boolean', 'hookStartPoint': 'integer', 'hookEndPoint': 'integer', 'eventLength': 'integer', 'linkEventName': 'string', 'linkStartTime': 'time', 'linkStartSlop': 'integer', 'linkEndSlop': 'integer', 'linkId': 'integer', 'linkEmbedded': 'boolean', 'extStartTime': 'time', 'extLength': 'integer', 'extCartName': 'string', 'extData': 'string', 'extEventId': 'integer', 'extAnncType': 'string' } handler=RivWebPyApi_ListHandler(base_tag='logLine',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def ListLogs(self,service_name='',log_name='',trackable=False, filter_string='',recent=False): """ Returns the metadata for a set of Rivendell logs (list of dictionaries). Takes the following arguments: service_name - Return only logs belonging to the specified service. Default is to return all logs. (string) log_name - Return only the specified log. Default is to return all logs. (string) trackable - If True, return will include only logs that contain one or more voice track slots. If False, all logs will be included. (boolean) filter_string - Return only logs that contain the filter_string in the Name, Description or Service fields. Default is to return all logs. (string) recent - Return the 14 most recently created logs. (boolean) """ # # Build the WebAPI arguments # trackarg='0' if(trackable): trackarg='1' recentarg='0' if(recent): recentarg='1' postdata={ 'COMMAND': '20', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'SERVICE_NAME': service_name, 'LOG_NAME': log_name, 'TRACKABLE': trackarg, 'FILTER': filter_string, 'RECENT': recentarg } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'name': 'string', 'serviceName': 'string', 'description': 'string', 'originUserName': 'string', 'originDatetime': 'datetime', 'linkDatetime': 'datetime', 'modifiedDatetime': 'datetime', 'purgeDate': 'date', 'autoRefresh': 'boolean', 'startDate': 'date', 'endDate': 'date', 'scheduledTracks': 'integer', 'completedTracks': 'integer', 'musicLinks': 'integer', 'musicLinked': 'boolean', 'trafficLinks': 'integer', 'trafficLinked': 'boolean' } handler=RivWebPyApi_ListHandler(base_tag='log',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def ListSchedulerCodes(self): """ Returns a list of all defined Rivendell schedule codes (list of dictionaries) """ # # Build the WebAPI arguments # postdata={ 'COMMAND': '24', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'code': 'string', 'description': 'string' } handler=RivWebPyApi_ListHandler(base_tag='schedCode',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def ListServices(self,trackable): """ Returns a set of Rivendell services (list of dictionaries). Takes one argument: trackable - Boolean. If True, return will include only services that are configured to support voice tracking. If False, all configured services for which the user has access will be included. """ # # Build the WebAPI arguments # trackarg='0' if(trackable): trackarg='1' postdata={ 'COMMAND': '21', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'TRACKABLE': trackarg } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'name': 'string', 'description': 'string' } handler=RivWebPyApi_ListHandler(base_tag='service',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def ListSystemSettings(self): """ Returns Rivendell system settings (list of dictionaries). """ # # Build the WebAPI arguments # postdata={ 'COMMAND': '33', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'realmName': 'string', 'sampleRate': 'integer', 'duplicateTitles': 'boolean', 'fixDuplicateTitles': 'boolean', 'maxPostLength': 'integer', 'isciXreferencePath': 'string', 'tempCartGroup': 'string', 'longDateFormat': 'string', 'shortDateFormat': 'string', 'showTwelveHourTime': 'boolean' } handler=RivWebPyApi_ListHandler(base_tag='systemSettings',fields=fields) xml.sax.parseString(r.text,handler) return handler.output() def RemoveCart(self,cart_number): """ Remove a cart from the cart library. Takes the following argument: cart_number - The number of the desired cart, in the range 1 - 999999 (integer) """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') # # Build the WebAPI arguments # postdata={ 'COMMAND': '13', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number) } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'ResponseCode': 'integer', 'ErrorString': 'string' } handler=RivWebPyApi_ListHandler(base_tag='RDWebResult',fields=fields) def RemoveCut(self,cart_number,cut_number): """ Remove an existing cut from an audio cart. Takes the following arguments: cart_number - The number of the desired cart, in the range 1 - 999999 (integer) cut_number - The number of the cut to remove, in the range 1 - 999 (integer) """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') if((cut_number<1)or(cut_number>999)): raise ValueError('invalid cut number') # # Build the WebAPI arguments # postdata={ 'COMMAND': '11', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number), 'CUT_NUMBER': str(cut_number) } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status() # # Generate the output dictionary # fields={ 'ResponseCode': 'integer', 'ErrorString': 'string' } handler=RivWebPyApi_ListHandler(base_tag='RDWebResult',fields=fields) def UnassignSchedCode(self,cart_number,sched_code): """ Unassign a schedule code from a cart. Takes the following arguments: cart_number - The number of the desired cart, in the range 1 - 999999 (integer) sched_code - An existing schedule code (integer). """ if((cart_number<1)or(cart_number>999999)): raise ValueError('invalid cart number') if(not sched_code): raise ValueError('invalid schedule code') # # Build the WebAPI arguments # postdata={ 'COMMAND': '26', 'LOGIN_NAME': self.__connection_username, 'PASSWORD': self.__connection_password, 'CART_NUMBER': str(cart_number), 'CODE': sched_code } # # Fetch the XML # r=requests.post(self.__connection_url,data=postdata) if(r.status_code!=requests.codes.ok): r.raise_for_status()