diff --git a/voctopublish/api_client/c3tt_rpc_client.py b/voctopublish/api_client/c3tt_rpc_client.py
index 3c319b1..669185b 100644
--- a/voctopublish/api_client/c3tt_rpc_client.py
+++ b/voctopublish/api_client/c3tt_rpc_client.py
@@ -36,7 +36,6 @@ def __init__(self, url, group, host, secret):
self.group = group
self.host = host
self.secret = secret
- self.ticket_id = None
def _gen_signature(self, method, args):
"""
@@ -72,16 +71,21 @@ def _gen_signature(self, method, args):
hash_ = hmac.new(bytes(self.secret, 'utf-8'), bytes(sig_args, 'utf-8'), hashlib.sha256)
return hash_.hexdigest()
- def _open_rpc(self, method, args=[]):
+ def _open_rpc(self, method, ticket=None, args=[]):
"""
create xmlrpc client
:param method:
+ :param ticket: optional, either a numeric ticket_id or an instance of Ticket class
:param args:
:return: attributes of the answer
"""
logging.debug('creating XML RPC proxy: ' + self.url + "?group=" + self.group + "&hostname=" + self.host)
- if self.ticket_id:
- args.insert(0, self.ticket_id)
+ if ticket is not None:
+ # the ticket parameter can be either a numeric ticket_id or an instance of Ticket class
+ if isinstance(ticket, int) or isinstance(ticket, str):
+ args.insert(0, ticket)
+ else:
+ args.insert(0, ticket.id)
try:
proxy = xmlrpc.client.ServerProxy(self.url + "?group=" + self.group + "&hostname=" + self.host)
@@ -139,49 +143,83 @@ def get_version(self):
"""
return str(self._open_rpc("C3TT.getVersion"))
- def assign_next_unassigned_for_state(self, ticket_type, to_state):
+ def assign_next_unassigned_for_state(self, ticket_type, to_state, property_filters = []):
"""
check for new ticket on tracker and get assignment
this also sets the ticket id in the c3tt client instance and has therefore be called before any ticket related
function
:param ticket_type: type of ticket
:param to_state: ticket state the returned ticket will be in after this call
+ :parm property_filters: return only tickets matching given properties
:return: ticket id or None in case no ticket is available for the type and state in the request
"""
- ret = self._open_rpc("C3TT.assignNextUnassignedForState", [ticket_type, to_state])
+ ret = self._open_rpc("C3TT.assignNextUnassignedForState", args=[ticket_type, to_state, property_filters])
# if we get no xml here there is no ticket for this job
if not ret:
return None
else:
- self.ticket_id = ret['id']
- return ret['id']
+ return ret
+
+ def get_assigned_for_state(self, ticket_type, state, property_filters = []):
+ """
+ Get first assigned ticket in state $state
+ function
+ :param ticket_type: type of ticket
+ :param to_state: ticket state the returned ticket will be in after this call
+ :parm property_filters: return only tickets matching given properties
+ :return: ticket id or None in case no ticket is available for the type and state in the request
+ """
+ ret = self._open_rpc("C3TT.getAssignedForState", args=[ticket_type, state, property_filters])
+ # if we get no xml here there is no ticket for this job
+ if not ret:
+ return None
+ else:
+ if len(ret) > 1:
+ logging.warn("multiple tickets assined, fetching first one")
+ return ret[0]
+
+ def get_tickets_for_state(self, ticket_type, to_state, property_filters = []):
+ """
+ Get all tickets in state $state from projects assigned to the workerGroup, unless workerGroup is halted
+ function
+ :param ticket_type: type of ticket
+ :param to_state: ticket state the returned ticket will be in after this call
+ :parm property_filters: return only tickets matching given properties
+ :return: ticket id or None in case no ticket is available for the type and state in the request
+ """
+ ret = self._open_rpc("C3TT.getTicketsForState", args=[ticket_type, to_state, property_filters])
+ # if we get no xml here there is no ticket for this job
+ if not ret:
+ return None
+ else:
+ return ret
- def set_ticket_properties(self, properties):
+ def set_ticket_properties(self, ticket, properties):
"""
set ticket properties
:param properties:
:return: Boolean
"""
- ret = self._open_rpc("C3TT.setTicketProperties", [properties])
+ ret = self._open_rpc("C3TT.setTicketProperties", ticket, args=[properties])
if not ret:
logging.error("no xml in answer")
return False
else:
return True
- def get_ticket_properties(self):
+ def get_ticket_properties(self, ticket):
"""
get ticket properties
:return:
"""
- ret = self._open_rpc("C3TT.getTicketProperties")
+ ret = self._open_rpc("C3TT.getTicketProperties", ticket)
if not ret:
logging.error("no xml in answer")
return None
else:
return ret
- def set_ticket_done(self):
+ def set_ticket_done(self, ticket):
"""
set Ticket status on done
:return:
@@ -189,19 +227,12 @@ def set_ticket_done(self):
ret = self._open_rpc("C3TT.setTicketDone")
logging.debug(str(ret))
- def set_ticket_failed(self, error):
+ def set_ticket_failed(self, ticket, error):
"""
set ticket status on failed an supply a error text
:param error:
"""
- self._open_rpc("C3TT.setTicketFailed", [error.encode('ascii', 'xmlcharrefreplace')])
-
- def get_ticket_id(self):
- """
- get the id of the ticket assigned to the client instance
- :return: Ticket id or None if no ID is assigned yet
- """
- return self.ticket_id
+ self._open_rpc("C3TT.setTicketFailed", ticket, [error.encode('ascii', 'xmlcharrefreplace')])
class C3TTException(Exception):
diff --git a/voctopublish/api_client/voctoweb_client.py b/voctopublish/api_client/voctoweb_client.py
index 7125ebc..0917a6a 100644
--- a/voctopublish/api_client/voctoweb_client.py
+++ b/voctopublish/api_client/voctoweb_client.py
@@ -22,7 +22,7 @@
import time
import tempfile
import operator
-import paramiko
+#import paramiko
import requests
import glob
@@ -265,6 +265,20 @@ def upload_file(self, local_filename, remote_filename, remote_folder):
logging.info("uploading " + remote_filename + " done")
+
+ def get_event(self):
+ """
+ Gets event on the voctoweb API host via GUID
+ :return:
+ """
+ try:
+ url = self.api_url[:-4] + "public/events/" + self.t.guid
+ print(url)
+ r = requests.get(url)
+ except requests.exceptions.BaseHTTPError as e_:
+ raise VoctowebException("error while checking event id with public API") from e_
+ return r
+
def create_or_update_event(self):
"""
Create a new event on the voctoweb API host
@@ -272,11 +286,6 @@ def create_or_update_event(self):
"""
logging.info('creating event on ' + self.api_url + ' in conference ' + self.t.voctoweb_slug)
- # prepare some variables for the api call
- url = self.api_url + 'events'
- if self.t.voctoweb_event_id:
- url += '/' + self.t.voctoweb_event_id
-
if self.t.url:
if self.t.url.startswith('//'):
event_url = 'https:' + self.t.url
@@ -297,6 +306,17 @@ def create_or_update_event(self):
for link in self.t.links:
description = '\n\n'.join([description, '' + link + ''])
+ images = {}
+ # only publish images if we already have them, which is not the case for relive only events
+ if hasattr(self.t, 'local_filename_base'):
+ images = {
+ 'thumb_filename': self.t.local_filename_base + ".jpg",
+ 'poster_filename': self.t.local_filename_base + "_preview.jpg",
+ 'timeline_filename': self.t.local_filename_base + ".timeline.jpg",
+ 'thumbnails_filename': self.t.local_filename_base + ".thumbnails.vtt",
+ 'release_date': str(time.strftime("%Y-%m-%d"))
+ }
+
# API code https://github.com/voc/voctoweb/blob/master/app/controllers/api/events_controller.rb
headers = {'CONTENT-TYPE': 'application/json'}
payload = {'api_key': self.api_key,
@@ -308,32 +328,36 @@ def create_or_update_event(self):
'subtitle': self.t.subtitle,
'link': event_url,
'original_language': self.t.languages[0],
- 'thumb_filename': self.t.local_filename_base + ".jpg",
- 'poster_filename': self.t.local_filename_base + "_preview.jpg",
- 'timeline_filename': self.t.local_filename_base + ".timeline.jpg",
- 'thumbnails_filename': self.t.local_filename_base + ".thumbnails.vtt",
- 'conference_id': self.t.voctoweb_slug,
+ #'conference_id': self.t.voctoweb_slug,
'description': description,
'date': self.t.date,
'persons': self.t.people,
'tags': self.t.voctoweb_tags,
'promoted': False,
- 'release_date': str(time.strftime("%Y-%m-%d"))
- }
- }
- logging.debug("api url: " + url + ' header: ' + str(headers) + ' payload: ' + str(payload))
-
+ **images
+ }
+ }
# call voctoweb api
try:
- # TODO make ssl verify a config option
- # r = requests.post(url, headers=headers, data=json.dumps(payload), verify=False)
+
+ # prepare some variables for the api call
+ url = self.api_url + 'events'
+ logging.debug("api url: " + url + ' header: ' + str(headers) + ' payload: ' + json.dumps(payload))
if self.t.voctoweb_event_id:
- r = requests.patch(url, headers=headers, data=json.dumps(payload))
+ try:
+ logging.info("trying to patch event " + self.t.guid)
+ r = requests.patch(url + '/' + self.t.guid, headers=headers, data=json.dumps(payload))
+ except:
+ logging.info("... faild, trying to create new event " + self.t.guid)
+ r = requests.post(url, headers=headers, data=json.dumps(payload))
else:
+ logging.info("trying to create new event " + self.t.guid)
r = requests.post(url, headers=headers, data=json.dumps(payload))
except requests.packages.urllib3.exceptions.MaxRetryError as e:
raise VoctowebException("Error during creation of event: " + str(e)) from e
+
+ print()
return r
def create_recording(self, local_filename, filename, folder, language, hq, html5, single_language=False):
diff --git a/voctopublish/create-event-by-ticket.py b/voctopublish/create-event-by-ticket.py
new file mode 100755
index 0000000..5f31a4f
--- /dev/null
+++ b/voctopublish/create-event-by-ticket.py
@@ -0,0 +1,249 @@
+#!/usr/bin/env python3
+# Copyright (C) 2017 derpeter
+# Copyright (C) 2019 andi
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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, see .
+
+import configparser
+import socket
+import sys
+import logging
+import os
+import subprocess
+import argparse
+import time
+
+from api_client.c3tt_rpc_client import C3TTClient
+from api_client.voctoweb_client import VoctowebClient
+from model.ticket_module import Ticket
+
+
+
+class RelivePublisher:
+ """
+ This is the main class for the Voctopublish application
+ It is meant to be used with the c3tt ticket tracker
+ """
+ def __init__(self, args = {}):
+ # load config
+ if not os.path.exists('/home/andi/relive/client.conf'):
+ raise IOError("Error: config file not found")
+
+ self.config = configparser.ConfigParser()
+ self.config.read('/home/andi/relive/client.conf')
+
+ self.notfail = args.notfail
+
+ # set up logging
+ logging.addLevelName(logging.WARNING, "\033[1;33m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
+ logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR))
+ logging.addLevelName(logging.INFO, "\033[1;32m%s\033[1;0m" % logging.getLevelName(logging.INFO))
+ logging.addLevelName(logging.DEBUG, "\033[1;85m%s\033[1;0m" % logging.getLevelName(logging.DEBUG))
+
+ self.logger = logging.getLogger()
+
+ sh = logging.StreamHandler(sys.stdout)
+ if self.config['general']['debug']:
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s {%(filename)s:%(lineno)d} %(message)s')
+ else:
+ formatter = logging.Formatter('%(asctime)s - %(message)s')
+
+ sh.setFormatter(formatter)
+ self.logger.addHandler(sh)
+ self.logger.setLevel(logging.DEBUG)
+
+ level = self.config['general']['debug']
+ if level == 'info':
+ self.logger.setLevel(logging.INFO if not args.verbose else logging.DEBUG)
+ elif level == 'warning':
+ self.logger.setLevel(logging.WARNING)
+ elif level == 'error':
+ self.logger.setLevel(logging.ERROR)
+ elif level == 'debug':
+ self.logger.setLevel(logging.DEBUG)
+
+ if self.config['C3Tracker']['host'] == "None":
+ self.host = socket.getfqdn()
+ else:
+ self.host = self.config['C3Tracker']['host']
+
+ self.ticket_type = 'encoding'
+ self.to_state = 'releasing'
+
+ logging.debug('creating C3TTClient')
+ try:
+ self.c3tt = C3TTClient(self.config['C3Tracker']['url'],
+ self.config['C3Tracker']['group'],
+ self.host,
+ self.config['C3Tracker']['secret'])
+ except Exception as e_:
+ raise PublisherException('Config parameter missing or empty, please check config') from e_
+
+ def create_event(self, ticket_id):
+ """
+ Decide based on the information provided by the tracker where to publish.
+ """
+ ticket = self._get_ticket_by_id(ticket_id)
+
+ if not ticket:
+ return
+
+ # voctoweb
+ if ticket.profile_voctoweb_enable and ticket.voctoweb_enable:
+ logging.debug(
+ 'encoding profile media flag: ' + str(ticket.profile_voctoweb_enable) + " project media flag: " + str(ticket.voctoweb_enable))
+ self._publish_event_to_voctoweb(ticket)
+
+ def _get_ticket_by_id(self, ticket_id):
+ """
+ Request a ticket from tracker
+ :return: a ticket object or None in case no ticket is available
+ """
+ logging.info('requesting ticket from tracker')
+ t = None
+
+ logging.info("Ticket ID:" + str(ticket_id))
+ try:
+ ticket_properties = self.c3tt.get_ticket_properties(ticket_id)
+ logging.debug("Ticket Properties: " + str(ticket_properties))
+ except Exception as e_:
+ raise e_
+ ticket_properties['EncodingProfile.Slug'] = 'relive'
+ ticket_properties['Publishing.Voctoweb.EnableProfile'] = 'yes'
+ t = Ticket({'id': ticket_id, 'parent_id': None}, ticket_properties)
+
+ return t
+
+
+
+ def _get_ticket_from_tracker(self):
+ """
+ Request the next unassigned ticket for the configured states
+ :return: a ticket object or None in case no ticket is available
+ """
+ logging.info('requesting ticket from tracker')
+ t = None
+
+ ticket_meta = None
+ # when we are in debug mode, we first check if we are already assigned to a ticket from previous run
+ if self.notfail:
+ ticket_meta = self.c3tt.get_assigned_for_state(self.ticket_type, self.to_state, {'EncodingProfile.Slug': 'relive'})
+ # otherwhise, or if that was not successful get the next unassigned one
+ if not ticket_meta:
+ ticket_meta = self.c3tt.assign_next_unassigned_for_state(self.ticket_type, self.to_state, {'EncodingProfile.Slug': 'relive'})
+
+ if ticket_meta:
+ ticket_id = ticket_meta['id']
+ logging.info("Ticket ID:" + str(ticket_id))
+ try:
+ ticket_properties = self.c3tt.get_ticket_properties(ticket_id)
+ logging.debug("Ticket Properties: " + str(ticket_properties))
+ except Exception as e_:
+ if not args.notfail:
+ self.c3tt.set_ticket_failed(ticket_id, e_)
+ raise e_
+ t = Ticket(ticket_meta, ticket_properties)
+ else:
+ logging.info('No ticket of type ' + self.ticket_type + ' for state ' + self.to_state)
+
+ return t
+
+ def _publish_event_to_voctoweb(self, ticket):
+ """
+ Create a event on an voctomix instance. This includes creating a recording for each media file.
+ """
+ try:
+ vw = VoctowebClient(ticket,
+ self.config['voctoweb']['api_key'],
+ self.config['voctoweb']['api_url'],
+ self.config['voctoweb']['ssh_host'],
+ self.config['voctoweb']['ssh_port'],
+ self.config['voctoweb']['ssh_user'])
+ except Exception as e_:
+ raise PublisherException('Error initializing voctoweb client. Config parameter missing') from e_
+
+ if not(ticket.voctoweb_event_id):
+ # if this is master ticket we need to check if we need to create an event on voctoweb
+
+ # check if event exists on voctoweb instance, and abort if this is already the case
+ r = vw.get_event()
+ if r.status_code == 204:
+ print(r.content)
+ print(r.status_code)
+ #self.c3tt.set_ticket_done(ticket)
+ print('event already exists, abort!')
+ print(os.path.join(ticket.voctoweb_url, ticket.slug))
+ return
+
+
+ logging.debug('this is a master ticket')
+ r = vw.create_or_update_event()
+ if r.status_code in [200, 201]:
+ logging.info("new event created or existing updated")
+ self.c3tt.set_ticket_properties(ticket.id, {'Voctoweb.EventId': r.json()['id']})
+
+ '''
+ try:
+ # we need to write the Event ID onto the parent ticket, so the other (master) encoding tickets
+ # also have acccess to the Voctoweb Event ID
+ self.c3tt.set_ticket_properties(ticket.parent_id, {'Voctoweb.EventId': r.json()['id']})
+ except Exception as e_:
+ raise PublisherException('failed to Voctoweb EventID to parent ticket') from e_
+ '''
+
+ elif r.status_code == 422:
+ # If this happens tracker and voctoweb are out of sync regarding the event id
+ # or we have some input error...
+ # todo: write voctoweb event_id to ticket properties --Andi
+ logging.warning("event already exists => please sync event manually")
+ else:
+ raise PublisherException('Voctoweb returned an error while creating an event: ' + str(r.status_code) + ' - ' + str(r.content))
+
+ print(os.path.join(ticket.voctoweb_url, ticket.slug))
+ else:
+ logging.info("nothing to to, is already pubilshed")
+ print(os.path.join(ticket.voctoweb_url, ticket.slug))
+ print("master? ", ticket.master )
+ print("event id:", ticket.voctoweb_event_id)
+
+
+
+ #self.c3tt.set_ticket_done(ticket)
+
+
+class PublisherException(Exception):
+ pass
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description='generate events on voctoweb for relive ')
+ parser.add_argument('ticket', action="store", help="integer id of an tracker ticket")
+ parser.add_argument('--verbose', '-v', action='store_true', default=False)
+ parser.add_argument('--notfail', action='store_true', default=False, help='do not mark ticket as failed in tracker when something goes wrong')
+
+ args = parser.parse_args()
+
+ try:
+ publisher = RelivePublisher(args)
+ except Exception as e:
+ logging.error(e)
+ logging.exception(e)
+ sys.exit(-1)
+
+ try:
+ publisher.create_event(args.ticket)
+ except Exception as e:
+ exc_type, exc_obj, exc_tb = sys.exc_info()
+ logging.exception(e)
+ sys.exit(-1)
diff --git a/voctopublish/create-events.py b/voctopublish/create-events.py
new file mode 100755
index 0000000..d81f8d4
--- /dev/null
+++ b/voctopublish/create-events.py
@@ -0,0 +1,226 @@
+#!/usr/bin/env python3
+# Copyright (C) 2017 derpeter
+# Copyright (C) 2019 andi
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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, see .
+
+import configparser
+import socket
+import sys
+import logging
+import os
+import subprocess
+import argparse
+import time
+
+from api_client.c3tt_rpc_client import C3TTClient
+from api_client.voctoweb_client import VoctowebClient
+from model.ticket_module import Ticket
+
+
+
+class RelivePublisher:
+ """
+ This is the main class for the Voctopublish application
+ It is meant to be used with the c3tt ticket tracker
+ """
+ def __init__(self, args = {}):
+ # load config
+ if not os.path.exists('client.conf'):
+ raise IOError("Error: config file not found")
+
+ self.config = configparser.ConfigParser()
+ self.config.read('client.conf')
+
+ self.notfail = args.notfail
+
+ # set up logging
+ logging.addLevelName(logging.WARNING, "\033[1;33m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
+ logging.addLevelName(logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR))
+ logging.addLevelName(logging.INFO, "\033[1;32m%s\033[1;0m" % logging.getLevelName(logging.INFO))
+ logging.addLevelName(logging.DEBUG, "\033[1;85m%s\033[1;0m" % logging.getLevelName(logging.DEBUG))
+
+ self.logger = logging.getLogger()
+
+ sh = logging.StreamHandler(sys.stdout)
+ if self.config['general']['debug']:
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s {%(filename)s:%(lineno)d} %(message)s')
+ else:
+ formatter = logging.Formatter('%(asctime)s - %(message)s')
+
+ sh.setFormatter(formatter)
+ self.logger.addHandler(sh)
+ self.logger.setLevel(logging.DEBUG)
+
+ level = self.config['general']['debug']
+ if level == 'info':
+ self.logger.setLevel(logging.INFO if not args.verbose else logging.DEBUG)
+ elif level == 'warning':
+ self.logger.setLevel(logging.WARNING)
+ elif level == 'error':
+ self.logger.setLevel(logging.ERROR)
+ elif level == 'debug':
+ self.logger.setLevel(logging.DEBUG)
+
+ if self.config['C3Tracker']['host'] == "None":
+ self.host = socket.getfqdn()
+ else:
+ self.host = self.config['C3Tracker']['host']
+
+ self.ticket_type = 'encoding'
+ self.to_state = 'releasing'
+
+ logging.debug('creating C3TTClient')
+ try:
+ self.c3tt = C3TTClient(self.config['C3Tracker']['url'],
+ self.config['C3Tracker']['group'],
+ self.host,
+ self.config['C3Tracker']['secret'])
+ except Exception as e_:
+ raise PublisherException('Config parameter missing or empty, please check config') from e_
+
+ def create_event(self):
+ """
+ Decide based on the information provided by the tracker where to publish.
+ """
+ ticket = self._get_ticket_from_tracker()
+
+ if not ticket:
+ return
+
+ # voctoweb
+ if ticket.profile_voctoweb_enable and ticket.voctoweb_enable:
+ logging.debug(
+ 'encoding profile media flag: ' + str(ticket.profile_voctoweb_enable) + " project media flag: " + str(ticket.voctoweb_enable))
+ self._publish_event_to_voctoweb(ticket)
+
+
+ def _get_ticket_from_tracker(self):
+ """
+ Request the next unassigned ticket for the configured states
+ :return: a ticket object or None in case no ticket is available
+ """
+ logging.info('requesting ticket from tracker')
+ t = None
+
+ ticket_meta = None
+ # when we are in debug mode, we first check if we are already assigned to a ticket from previous run
+ if self.notfail:
+ ticket_meta = self.c3tt.get_assigned_for_state(self.ticket_type, self.to_state, {'EncodingProfile.Slug': 'relive'})
+ # otherwhise, or if that was not successful get the next unassigned one
+ if not ticket_meta:
+ ticket_meta = self.c3tt.assign_next_unassigned_for_state(self.ticket_type, self.to_state, {'EncodingProfile.Slug': 'relive'})
+
+ if ticket_meta:
+ ticket_id = ticket_meta['id']
+ logging.info("Ticket ID:" + str(ticket_id))
+ try:
+ ticket_properties = self.c3tt.get_ticket_properties(ticket_id)
+ logging.debug("Ticket Properties: " + str(ticket_properties))
+ except Exception as e_:
+ if not args.notfail:
+ self.c3tt.set_ticket_failed(ticket_id, e_)
+ raise e_
+ t = Ticket(ticket_meta, ticket_properties)
+ else:
+ logging.info('No ticket of type ' + self.ticket_type + ' for state ' + self.to_state)
+
+ return t
+
+ def _publish_event_to_voctoweb(self, ticket):
+ """
+ Create a event on an voctomix instance. This includes creating a recording for each media file.
+ """
+ try:
+ vw = VoctowebClient(ticket,
+ self.config['voctoweb']['api_key'],
+ self.config['voctoweb']['api_url'],
+ self.config['voctoweb']['ssh_host'],
+ self.config['voctoweb']['ssh_port'],
+ self.config['voctoweb']['ssh_user'])
+ except Exception as e_:
+ raise PublisherException('Error initializing voctoweb client. Config parameter missing') from e_
+
+ if not(ticket.voctoweb_event_id):
+ # if this is master ticket we need to check if we need to create an event on voctoweb
+
+ # check if event exists on voctoweb instance, and abort if this is already the case
+ r = vw.get_event()
+ if r.status_code == 204:
+ print(r.content)
+ print(r.status_code)
+ #self.c3tt.set_ticket_done(ticket)
+ print('event already exists, abort!')
+ print(os.path.join(ticket.voctoweb_url, ticket.slug))
+ return
+
+
+ logging.debug('this is a master ticket')
+ r = vw.create_or_update_event()
+ if r.status_code in [200, 201]:
+ logging.info("new event created or existing updated")
+
+ '''
+ try:
+ # we need to write the Event ID onto the parent ticket, so the other (master) encoding tickets
+ # also have acccess to the Voctoweb Event ID
+ self.c3tt.set_ticket_properties(ticket.parent_id, {'Voctoweb.EventId': r.json()['id']})
+ except Exception as e_:
+ raise PublisherException('failed to Voctoweb EventID to parent ticket') from e_
+ '''
+
+ elif r.status_code == 422:
+ # If this happens tracker and voctoweb are out of sync regarding the event id
+ # or we have some input error...
+ # todo: write voctoweb event_id to ticket properties --Andi
+ logging.warning("event already exists => please sync event manually")
+ else:
+ raise PublisherException('Voctoweb returned an error while creating an event: ' + str(r.status_code) + ' - ' + str(r.content))
+
+ print(os.path.join(ticket.voctoweb_url, ticket.slug))
+ else:
+ logging.info("nothing to to, is already pubilshed")
+ print(os.path.join(ticket.voctoweb_url, ticket.slug))
+ print("master? ", ticket.master )
+ print("event id:", ticket.voctoweb_event_id)
+
+
+
+ self.c3tt.set_ticket_done(ticket)
+
+
+class PublisherException(Exception):
+ pass
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description='generate events on voctoweb for relive ')
+ parser.add_argument('--verbose', '-v', action='store_true', default=False)
+ parser.add_argument('--notfail', action='store_true', default=False, help='do not mark ticket as failed in tracker when something goes wrong')
+
+ args = parser.parse_args()
+
+ try:
+ publisher = RelivePublisher(args)
+ except Exception as e:
+ logging.error(e)
+ logging.exception(e)
+ sys.exit(-1)
+
+ try:
+ publisher.create_event()
+ except Exception as e:
+ exc_type, exc_obj, exc_tb = sys.exc_info()
+ logging.exception(e)
+ sys.exit(-1)
diff --git a/voctopublish/model/ticket_module.py b/voctopublish/model/ticket_module.py
index 8040010..3a5bf37 100644
--- a/voctopublish/model/ticket_module.py
+++ b/voctopublish/model/ticket_module.py
@@ -23,28 +23,18 @@ class Ticket:
and adds some additional information.
"""
- def __init__(self, ticket, ticket_id):
- if not ticket:
- raise TicketException('Ticket was None type')
- self.__tracker_ticket = ticket
- self.ticket_id = ticket_id
+ def __init__(self, ticket_meta, ticket_properties):
+ if not ticket_properties:
+ raise TicketException('Ticket properties was None type')
+ self.__tracker_ticket_meta = ticket_meta
+ self.__tracker_ticket = ticket_properties
+
+ self.id = ticket_meta['id']
+ self.parent_id = ticket_meta['parent_id']
# project properties
self.acronym = self._validate_('Project.Slug')
- # encoding profile properties
- if self._validate_('EncodingProfile.IsMaster') == 'yes':
- self.master = True
- else:
- self.master = False
- self.profile_extension = self._validate_('EncodingProfile.Extension')
- self.profile_slug = self._validate_('EncodingProfile.Slug')
- self.filename = self._validate_('EncodingProfile.Basename') + "." + self.profile_extension
- self.folder = self._validate_('EncodingProfile.MirrorFolder')
-
- # encoding properties
- self.language_index = self._validate_('Encoding.LanguageIndex', True)
-
# fahrplan properties
self.slug = self._validate_('Fahrplan.Slug')
self.guid = self._validate_('Fahrplan.GUID')
@@ -53,65 +43,85 @@ def __init__(self, ticket, ticket_id):
self.subtitle = self._validate_('Fahrplan.Subtitle', True)
self.abstract = self._validate_('Fahrplan.Abstract', True)
self.description = self._validate_('Fahrplan.Description', True)
- self.date = self._validate_('Fahrplan.Date')
- self.local_filename = self.fahrplan_id + "-" + self.profile_slug + "." + self.profile_extension
- self.local_filename_base = self.fahrplan_id + "-" + self.profile_slug
+ self.date = self._validate_('Fahrplan.DateTime')
self.room = self._validate_('Fahrplan.Room')
self.people = []
- if 'Fahrplan.Person_list' in ticket:
+ if 'Fahrplan.Person_list' in ticket_properties:
self.people = self._validate_('Fahrplan.Person_list').split(', ')
self.links = []
- if 'Fahrplan.Links' in ticket:
+ if 'Fahrplan.Links' in ticket_properties:
self.links = self._validate_('Fahrplan.Links', True).split(' ')
# the following are arguments that my not be present in every fahrplan
self.track = self._validate_('Fahrplan.Track', True)
self.day = self._validate_('Fahrplan.Day', True)
self.url = self._validate_('Fahrplan.URL', True)
- self.date = self._validate_('Fahrplan.Date')
-
- # recording ticket properties
-
- # special case languages: if Encoding.Language is present, it overrides Record.Language:
- if 'Encoding.Language' in ticket:
- self.language = self._validate_('Encoding.Language')
- self.languages = dict(enumerate(self._validate_('Encoding.Language').split('-')))
- else:
- self.language = self._validate_('Record.Language')
- self.languages = {int(k.split('.')[-1]): self._validate_(k) for k in self.__tracker_ticket.keys() if k.startswith('Record.Language.')}
- self.language_template = self._validate_('Encoding.LanguageTemplate')
# general publishing properties
self.publishing_path = self._validate_('Publishing.Path')
self.publishing_tags = self._validate_('Publishing.Tags', True)
- # youtube properties
- if self._validate_('Publishing.YouTube.EnableProfile') == 'yes':
- self.profile_youtube_enable = True
- else:
- self.profile_youtube_enable = False
- if self._validate_('Publishing.YouTube.Enable') == 'yes':
- self.youtube_enable = True
+
+ # encoding (profile) properties
+ #if self._validate_('EncodingProfile.IsMaster') == 'yes':
+ # self.master = True
+ #else:
+ self.master = False
+ self.profile_slug = self._validate_('EncodingProfile.Slug')
+ print('Ticket Type:', self.profile_slug)
+ if self.profile_slug == 'relive':
+ # TODO: map two char language codes to three char ones in a more proper way...
+ lang_map = {'en': 'eng', 'de': 'deu'} # WORKAROUND
+ self.language = lang_map[self._validate_('Fahrplan.Language')]
+ self.languages = {0: self.language}
else:
- self.youtube_enable = False
- # we will fill the following variables only if youtube is enabled
- if self.profile_youtube_enable and self.youtube_enable:
- self.youtube_token = self._validate_('Publishing.YouTube.Token')
- self.youtube_category = self._validate_('Publishing.YouTube.Category', True)
- self.youtube_privacy = self._validate_('Publishing.YouTube.Privacy', True)
- self.youtube_tags = self._validate_('Publishing.YouTube.Tags', True)
- self.youtube_title_prefix = self._validate_('Publishing.YouTube.TitlePrefix', True)
- self.youtube_title_prefix_speakers = self._validate_('Publishing.YouTube.TitlePrefixSpeakers', True)
- self.youtube_title_suffix = self._validate_('Publishing.YouTube.TitleSuffix', True)
- # check if this event has already been published to youtube
- if 'YouTube.Url0' in ticket and self._validate_('YouTube.Url0') is not None:
- self.has_youtube_url = True
+ # encoding (profile) properties
+ self.profile_extension = self._validate_('EncodingProfile.Extension', optional=True)
+ self.filename = self._validate_('EncodingProfile.Basename') + "." + self.profile_extension
+ self.folder = self._validate_('EncodingProfile.MirrorFolder')
+ self.language_index = self._validate_('Encoding.LanguageIndex', True)
+ self.local_filename = self.fahrplan_id + "-" + self.profile_slug + "." + self.profile_extension
+ self.local_filename_base = self.fahrplan_id + "-" + self.profile_slug
+
+ # recording ticket properties
+
+ # special case languages: if Encoding.Language is present, it overrides Record.Language:
+ if 'Encoding.Language' in ticket_properties:
+ self.language = self._validate_('Encoding.Language')
+ self.languages = dict(enumerate(self._validate_('Encoding.Language').split('-')))
+ else:
+ self.language = self._validate_('Record.Language')
+ self.languages = {int(k.split('.')[-1]): self._validate_(k) for k in self.__tracker_ticket.keys() if k.startswith('Record.Language.')}
+ self.language_template = self._validate_('Encoding.LanguageTemplate')
+
+
+ # youtube properties
+ if self._validate_('Publishing.YouTube.EnableProfile') == 'yes':
+ self.profile_youtube_enable = True
else:
- self.has_youtube_url = False
- if self._validate_('Publishing.YouTube.Playlists', True) is not None:
- self.youtube_playlists = self._validate_('Publishing.YouTube.Playlists', True).split(',')
+ self.profile_youtube_enable = False
+ if self._validate_('Publishing.YouTube.Enable') == 'yes':
+ self.youtube_enable = True
else:
- self.youtube_playlists = []
- self.youtube_urls = ''
+ self.youtube_enable = False
+ # we will fill the following variables only if youtube is enabled
+ if self.profile_youtube_enable and self.youtube_enable:
+ self.youtube_token = self._validate_('Publishing.YouTube.Token')
+ self.youtube_category = self._validate_('Publishing.YouTube.Category', True)
+ self.youtube_privacy = self._validate_('Publishing.YouTube.Privacy', True)
+ self.youtube_tags = self._validate_('Publishing.YouTube.Tags', True)
+ self.youtube_title_prefix = self._validate_('Publishing.YouTube.TitlePrefix', True)
+ self.youtube_title_prefix_speakers = self._validate_('Publishing.YouTube.TitlePrefixSpeakers', True)
+ self.youtube_title_suffix = self._validate_('Publishing.YouTube.TitleSuffix', True)
+ # check if this event has already been published to youtube
+ if 'YouTube.Url0' in ticket_properties and self._validate_('YouTube.Url0') is not None:
+ self.has_youtube_url = True
+ else:
+ self.has_youtube_url = False
+ if self._validate_('Publishing.YouTube.Playlists', True) is not None:
+ self.youtube_playlists = self._validate_('Publishing.YouTube.Playlists', True).split(',')
+ else:
+ self.youtube_playlists = []
+ self.youtube_urls = ''
# voctoweb properties
if self._validate_('Publishing.Voctoweb.EnableProfile') == 'yes':
@@ -126,16 +136,16 @@ def __init__(self, ticket, ticket_id):
self.voctoweb_url = self._validate_('Publishing.Voctoweb.Url', True)
# we will fill the following variables only if voctoweb is enabled
if self.profile_voctoweb_enable and self.voctoweb_enable:
- self.mime_type = self._validate_('Publishing.Voctoweb.MimeType')
+ self.mime_type = self._validate_('Publishing.Voctoweb.MimeType', True)
self.voctoweb_thump_path = self._validate_('Publishing.Voctoweb.Thumbpath')
self.voctoweb_path = self._validate_('Publishing.Voctoweb.Path')
self.voctoweb_slug = self._validate_('Publishing.Voctoweb.Slug')
self.voctoweb_tags = [self.acronym, self.fahrplan_id, self.date.split('-')[0]]
if self.track:
self.voctoweb_tags.append(self.track)
- if 'Publishing.Voctoweb.Tags' in ticket:
+ if 'Publishing.Voctoweb.Tags' in ticket_properties:
self.voctoweb_tags += self._validate_('Publishing.Voctoweb.Tags').replace(' ', '').split(',')
- if 'Publishing.Tags' in ticket:
+ if 'Publishing.Tags' in ticket_properties:
self.voctoweb_tags += self._validate_('Publishing.Tags').replace(' ', '').split(',')
self.recording_id = self._validate_('Voctoweb.RecordingId.Master', True)
self.voctoweb_event_id = self._validate_('Voctoweb.EventId', True)
diff --git a/voctopublish/test/api_client_test/test_c3tt_rpc_client.py b/voctopublish/test/api_client_test/test_c3tt_rpc_client.py
index c379942..fda2084 100644
--- a/voctopublish/test/api_client_test/test_c3tt_rpc_client.py
+++ b/voctopublish/test/api_client_test/test_c3tt_rpc_client.py
@@ -15,7 +15,6 @@ def setUp(self):
def test_init(self):
assert self._client.url == "rpc" # todo shouldn't this be an _url join?
- assert self._client.ticket_id is None
def test_gen_signature_args_empty(self):
hash_ = self._client._gen_signature("test", [])
diff --git a/voctopublish/voctopublish.py b/voctopublish/voctopublish.py
index e0e6904..c369e2a 100755
--- a/voctopublish/voctopublish.py
+++ b/voctopublish/voctopublish.py
@@ -16,6 +16,7 @@
# along with this program. If not, see .
import configparser
+import argparse
import socket
import sys
import logging
@@ -35,13 +36,14 @@ class Publisher:
This is the main class for the Voctopublish application
It is meant to be used with the c3tt ticket tracker
"""
- def __init__(self):
+ def __init__(self, args = {}):
# load config
if not os.path.exists('client.conf'):
raise IOError("Error: config file not found")
self.config = configparser.ConfigParser()
self.config.read('client.conf')
+ self.notfail = args.notfail
# set up logging
logging.addLevelName(logging.WARNING, "\033[1;33m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
@@ -63,7 +65,7 @@ def __init__(self):
level = self.config['general']['debug']
if level == 'info':
- self.logger.setLevel(logging.INFO)
+ self.logger.setLevel(logging.INFO if not args.verbose else logging.DEBUG)
elif level == 'warning':
self.logger.setLevel(logging.WARNING)
elif level == 'error':
@@ -126,7 +128,7 @@ def publish(self):
"encoding profile youtube flag: " + str(self.ticket.profile_youtube_enable) + ' project youtube flag: ' + str(self.ticket.youtube_enable))
self._publish_to_youtube()
- self.c3tt.set_ticket_done()
+ self.c3tt.set_ticket_done(self.ticket)
# Twitter
if self.ticket.twitter_enable and self.ticket.master:
@@ -143,16 +145,26 @@ def _get_ticket_from_tracker(self):
"""
logging.info('requesting ticket from tracker')
t = None
- ticket_id = self.c3tt.assign_next_unassigned_for_state(self.ticket_type, self.to_state)
- if ticket_id:
+
+ ticket_meta = None
+ # when we are in notfail aka debug mode, we first check if we are already assigned to a ticket from previous run
+ if self.notfail:
+ ticket_meta = self.c3tt.get_assigned_for_state(self.ticket_type, self.to_state, {'EncodingProfile.Slug': 'relive'})
+ # otherwhise, or if that was not successful get the next unassigned one
+ if not ticket_meta:
+ ticket_meta = self.c3tt.assign_next_unassigned_for_state(self.ticket_type, self.to_state, {'EncodingProfile.Slug': 'relive'})
+
+ if ticket_meta:
+ ticket_id = ticket_meta['id']
logging.info("Ticket ID:" + str(ticket_id))
try:
- tracker_ticket = self.c3tt.get_ticket_properties()
- logging.debug("Ticket: " + str(tracker_ticket))
+ ticket_properties = self.c3tt.get_ticket_properties(ticket_id)
+ logging.debug("Ticket Properties: " + str(ticket_properties))
except Exception as e_:
- self.c3tt.set_ticket_failed(e_)
+ if not args.notfail:
+ self.c3tt.set_ticket_failed(ticket_id, e_)
raise e_
- t = Ticket(tracker_ticket, ticket_id)
+ t = Ticket(ticket_meta, ticket_properties)
else:
logging.info('No ticket of type ' + self.ticket_type + ' for state ' + self.to_state)
@@ -190,7 +202,7 @@ def _publish_to_voctoweb(self):
logging.debug('response: ' + str(r.json()))
try:
# todo: only set recording id when new recording was created, and not when it was only updated
- self.c3tt.set_ticket_properties({'Voctoweb.EventId': r.json()['id']})
+ self.c3tt.set_ticket_properties(self.ticket, {'Voctoweb.EventId': r.json()['id']})
except Exception as e_:
raise PublisherException('failed to Voctoweb EventID to ticket') from e_
@@ -199,7 +211,7 @@ def _publish_to_voctoweb(self):
# todo: write voctoweb event_id to ticket properties --Andi
logging.warning("event already exists => publishing")
else:
- raise PublisherException('Voctoweb returned an error while creating an event: ' + str(r.status_code) + ' - ' + str(r.content))
+ raise PublisherException('Voctoweb returned an error while creating an event: ' + str(r.status_code) + ' - ' + str(r.content))
# in case of a multi language release we create here the single language files
if len(self.ticket.languages) > 1:
@@ -239,7 +251,7 @@ def _publish_to_voctoweb(self):
# when the ticket was created, and not only updated: write recording_id to ticket
if recording_id:
- self.c3tt.set_ticket_properties({'Voctoweb.RecordingId.Master': recording_id})
+ self.c3tt.set_ticket_properties(self.ticket, {'Voctoweb.RecordingId.Master': recording_id})
def _mux_to_single_language(self, vw):
"""
@@ -276,7 +288,7 @@ def _mux_to_single_language(self, vw):
try:
# when the ticket was created, and not only updated: write recording_id to ticket
if recording_id:
- self.c3tt.set_ticket_properties({'Voctoweb.RecordingId.' + self.ticket.languages[language]: str(recording_id)})
+ self.c3tt.set_ticket_properties(self.ticket, {'Voctoweb.RecordingId.' + self.ticket.languages[language]: str(recording_id)})
except Exception as e_:
raise PublisherException('failed to set RecordingId to ticket') from e_
@@ -294,7 +306,7 @@ def _publish_to_youtube(self):
for i, youtubeUrl in enumerate(youtube_urls):
props['YouTube.Url' + str(i)] = youtubeUrl
- self.c3tt.set_ticket_properties(props)
+ self.c3tt.set_ticket_properties(self.ticket, props)
self.ticket.youtube_urls = props
# now, after we reported everything back to the tracker, we try to add the videos to our own playlists
@@ -317,6 +329,11 @@ class PublisherException(Exception):
if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--verbose', '-v', action='store_true', default=False)
+ parser.add_argument('--notfail', action='store_true', default=False, help='do not mark ticket as failed in tracker when something goes wrong')
+
+ args = parser.parse_args()
try:
publisher = Publisher()
except Exception as e:
@@ -328,6 +345,7 @@ class PublisherException(Exception):
publisher.publish()
except Exception as e:
exc_type, exc_obj, exc_tb = sys.exc_info()
- publisher.c3tt.set_ticket_failed('%s: %s' % (exc_type.__name__, e))
+ if not args.notfail:
+ publisher.c3tt.set_ticket_failed(publisher.ticket.id, '%s: %s' % (exc_type.__name__, e))
logging.exception(e)
sys.exit(-1)