From e5da40a104c80cf234dbd7c112bac7848d624fa4 Mon Sep 17 00:00:00 2001 From: j <0x006A@0x2620.org> Date: Fri, 20 Apr 2012 12:22:32 +0200 Subject: [PATCH] first round of makeing png/js/script/config overwrite more consistent --- .bzrignore | 1 + README | 2 +- pandora/app/config.py | 37 +- pandora/{0xdb.jsonc => config.0xdb.jsonc} | 2 +- pandora/{padma.jsonc => config.padma.jsonc} | 0 pandora/config.pandora.jsonc | 692 ++++++++++++++++++ pandora/manage.py | 4 + pandora/settings.py | 11 +- pandora/templates/api.html | 2 +- pandora/templates/index.html | 8 +- pandora/templates/item.html | 6 +- scripts/{item_icon => item_icon.pandora.py} | 0 scripts/{list_icon => list_icon.pandora.py} | 0 scripts/{oxdb_poster => poster.0xdb.py} | 2 +- scripts/{padma_poster => poster.padma.py} | 2 +- scripts/poster.pandora.py | 86 +++ static/favicon.ico | 2 +- static/html/50x.html | 2 +- static/js/pandora.js | 4 +- static/js/pandora/account.js | 6 +- static/js/pandora/allItems.js | 2 +- static/js/pandora/annotationDialog.js | 2 +- static/js/pandora/contactForm.js | 2 +- static/js/pandora/deleteItemDialog.js | 2 +- static/js/pandora/deleteListDialog.js | 2 +- static/js/pandora/error.js | 4 +- static/js/pandora/folderBrowserList.js | 2 +- static/js/pandora/folderList.js | 2 +- static/js/pandora/folders.js | 2 +- static/js/pandora/home.js | 4 +- static/js/pandora/home.padma.js | 4 +- static/js/pandora/homePage.js | 2 +- static/js/pandora/listDialog.js | 2 +- static/js/pandora/makeListPrivateDialog.js | 2 +- static/js/pandora/preferencesDialog.js | 2 +- static/js/pandora/siteDialog.js | 4 +- static/js/pandora/tv.js | 2 +- static/js/pandora/usersDialog.js | 2 +- static/png/icon.0xdb.org | Bin 0 -> 16670 bytes static/png/icon.padma.png | Bin 0 -> 13574 bytes .../{pandora/icon256.png => icon.pandora.png} | Bin static/png/logo.0xdb.org | Bin 0 -> 32274 bytes static/png/logo.padma.png | Bin 0 -> 47483 bytes .../{pandora/logo256.png => logo.pandora.png} | Bin static/png/pandora/icon16.png | Bin 3464 -> 0 bytes static/png/pandora/icon64.png | Bin 7535 -> 0 bytes static/png/rights.png | Bin 0 -> 162122 bytes static/png/software.png | Bin 0 -> 55403 bytes vm/firstboot.sh | 14 +- 49 files changed, 860 insertions(+), 67 deletions(-) rename pandora/{0xdb.jsonc => config.0xdb.jsonc} (99%) rename pandora/{padma.jsonc => config.padma.jsonc} (100%) create mode 100644 pandora/config.pandora.jsonc rename scripts/{item_icon => item_icon.pandora.py} (100%) rename scripts/{list_icon => list_icon.pandora.py} (100%) rename scripts/{oxdb_poster => poster.0xdb.py} (98%) rename scripts/{padma_poster => poster.padma.py} (98%) create mode 100755 scripts/poster.pandora.py create mode 100644 static/png/icon.0xdb.org create mode 100644 static/png/icon.padma.png rename static/png/{pandora/icon256.png => icon.pandora.png} (100%) create mode 100644 static/png/logo.0xdb.org create mode 100644 static/png/logo.padma.png rename static/png/{pandora/logo256.png => logo.pandora.png} (100%) delete mode 100644 static/png/pandora/icon16.png delete mode 100644 static/png/pandora/icon64.png create mode 100644 static/png/rights.png create mode 100644 static/png/software.png diff --git a/.bzrignore b/.bzrignore index b87a85b5..fc966d88 100644 --- a/.bzrignore +++ b/.bzrignore @@ -7,3 +7,4 @@ bin static/json/pandora.json static/js/pandora.min.js static/png/*.png +config.jsonc diff --git a/README b/README index ce631a3e..0d6c37c4 100644 --- a/README +++ b/README @@ -38,7 +38,7 @@ Get code from bazzar create local_settings.py and create site.jsonc in /srv/pandora/pandora do noy copy settings.py but only add your changes to local_settings.py (check https://wiki.0x2620.org/wiki/pandora/configuration - and use settings.py / 0xdb.jsonc / padma.jsonc as example) + and use settings.py / config.jsonc / config.*.jsonc as example) create and configure database as described below after that diff --git a/pandora/app/config.py b/pandora/app/config.py index 206887a2..c8b7ca3c 100644 --- a/pandora/app/config.py +++ b/pandora/app/config.py @@ -90,7 +90,7 @@ def update_static(): with open(f) as j: data += j.read() + '\n' js += [ - 'png/icon64.png', + 'png/icon.png', ] print 'write', pandora_js with open(pandora_js, 'w') as f: @@ -109,24 +109,29 @@ def update_static(): if os.path.splitext(f)[-1] in ('.js', '.json'): f = os.path.join(root, f) os.system('gzip -9 -c "%s" > "%s.gz"' % (f, f)) - - for size in (16, 64, 256): - pandora = os.path.join(settings.STATIC_ROOT, 'png/pandora/icon%d.png'%size) - image = os.path.join(settings.STATIC_ROOT, 'png/icon%d.png'%size) + + for name in ('logo', 'icon'): + site = os.path.join(settings.STATIC_ROOT, 'png/%s.%s.png'%(name, settings.CONFIG['site']['id'])) + pandora = os.path.join(settings.STATIC_ROOT, 'png/%s.pandora.png'%name) + image = os.path.join(settings.STATIC_ROOT, 'png/%s.png'%name) if not os.path.exists(image): - shutil.copyfile(pandora, image) + if os.path.exists(site): + shutil.copyfile(site, image) + else: + shutil.copyfile(pandora, image) - for size in (256, ): - pandora = os.path.join(settings.STATIC_ROOT, 'png/pandora/logo%d.png'%size) - image = os.path.join(settings.STATIC_ROOT, 'png/logo%d.png'%size) - if not os.path.exists(image): - shutil.copyfile(pandora, image) #download geo data update_geoip() - #poster script - if not os.path.exists(settings.ITEM_POSTER): - os.symlink(settings.ITEM_POSTER.replace('poster', 'oxdb_poster'), - settings.ITEM_POSTER) + + #scripts + for script in (settings.ITEM_POSTER, settings.ITEM_ICON, settings.LIST_ICON): + if not os.path.exists(script): + site_script = script.replace('.py', '.%s.py' % settings.CONFIG['site']['id']) + default_script = script.replace('.py', '.pandora.py') + if os.path.exists(site_script): + os.symlink(site_script, script) + else: + os.symlink(default_script, script) def update_geoip(force=False): path = os.path.join(settings.GEOIP_PATH, 'GeoLiteCity.dat') @@ -138,6 +143,6 @@ def update_geoip(force=False): os.unlink(path) os.system('gunzip "%s.gz"' % path) -def init(): +def init(): load_config() thread.start_new_thread(reloader_thread, ()) diff --git a/pandora/0xdb.jsonc b/pandora/config.0xdb.jsonc similarity index 99% rename from pandora/0xdb.jsonc rename to pandora/config.0xdb.jsonc index 9f8c68e3..417464ec 100644 --- a/pandora/0xdb.jsonc +++ b/pandora/config.0xdb.jsonc @@ -645,7 +645,7 @@ // E-mail address uses by the system (from) "system": "0xDB@0xDB.org" }, - "id": "oxdb", + "id": "0xdb", "name": "0xDB", "url": "0xDB.org", "videoprefix": "" diff --git a/pandora/padma.jsonc b/pandora/config.padma.jsonc similarity index 100% rename from pandora/padma.jsonc rename to pandora/config.padma.jsonc diff --git a/pandora/config.pandora.jsonc b/pandora/config.pandora.jsonc new file mode 100644 index 00000000..03ef9128 --- /dev/null +++ b/pandora/config.pandora.jsonc @@ -0,0 +1,692 @@ +/* + Pan.do/ra Settings + + You can edit this file. +*/ +{ + "additionalSort": [ + {"key": "title", "operator": "+"} + ], + "annotations": { + "showUsers": true + }, + /* + Capabilities are per user level. + They can either be general: + {level: true} means a user of that level has the capability) + or related to items: + {level: x} means a user of that level has the capability + for items of a rights level up to and including x + */ + "capabilities": { + "canDeleteItems": {"admin": true}, + "canDownloadVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4}, + "canEditAnnotations": {"staff": true, "admin": true}, + "canEditEvents": {"staff": true, "admin": true}, + "canEditFeaturedLists": {"staff": true, "admin": true}, + "canEditMetadata": {"staff": true, "admin": true}, + "canEditPlaces": {"staff": true, "admin": true}, + "canEditSitePages": {"staff": true, "admin": true}, + "canEditUsers": {"admin": true}, + "canImportAnnotations": {"member": true, "staff": true, "admin": true}, + "canManageTitlesAndNames": {"member": true, "staff": true, "admin": true}, + "canManagePlacesAndEvents": {"member": true, "staff": true, "admin": true}, + "canManageUsers": {"staff": true, "admin": true}, + "canPlayClips": {"guest": 1, "member": 1, "staff": 4, "admin": 4}, + "canPlayVideo": {"guest": 1, "member": 1, "staff": 4, "admin": 4}, + "canSeeAccessed": {"staff": true, "admin": true}, + "canSeeDebugMenu": {"staff": true, "admin": true}, + "canSeeExtraItemViews": {"staff": true, "admin": true}, + "canSeeFiles": {"staff": true, "admin": true}, + "canSeeItem": {"guest": 1, "member": 1, "staff": 4, "admin": 4}, + "canSeeSize": {"staff": true, "admin": true}, + "canSendMail": {"staff": true, "admin": true}, + "canUploadVideo": {"guest": false, "member": true, "staff": true, "admin": true} + }, + /* + clipKeys are the properties that clips can by sorted by. + If sortOperator is not specified, it will be + for strings and - for numbers. + */ + "clipKeys": [ + {"id": "text", "title": "Text", "type": "string"}, + {"id": "position", "title": "Position", "type": "float", "sortOperator": "+"}, + {"id": "duration", "title": "Duration", "type": "float"}, + {"id": "hue", "title": "Hue", "type": "float", "sortOperator": "+"}, + {"id": "saturation", "title": "Saturation", "type": "float"}, + {"id": "lightness", "title": "Lightness", "type": "float"}, + {"id": "volume", "title": "Volume", "type": "float"} + ], + /* + clipLayers is the ordered list of public layers that will appear as the + text of clips. Excluding a layer from this list means it will not be + included in find annotations. // FIXME: the last bit is not implemented. + */ + "clipLayers": ["transcripts", "keywords", "places", "events", "descriptions"], + // fixme: either this, or filter: true in itemKeys, but not both + "filters": [ + {"id": "source", "title": "Sources", "type": "string"}, + {"id": "project", "title": "Projects", "type": "string"}, + {"id": "topic", "title": "Topics", "type": "string"}, + {"id": "name", "title": "People", "type": "string"}, + {"id": "language", "title": "Languages", "type": "string"}, + {"id": "license", "title": "License", "type": "string"}, + {"id": "places", "title": "Places", "type": "string"}, + {"id": "events", "title": "Events", "type": "string"}, + {"id": "keywords", "title": "Keywords", "type": "string"} + ], + /* + An itemKey must have the following properties: + id: The id of the key (as known by the server) + title: The title of the key (as displayed by the client) + type: text, string, float, integer, or array of any of these + and can have any of the following properties: + autocomplete: If true, find element will autocomplete + autocompleteSortKey: The key that suggestions will be sorted by + capability: A capability required to see this key + columnRequired: If true, the column can't be removed + columnWidth: Default column width in px + filter: if true, one can filter results by this key + find: If true, this key will appear as a find option + format: {type: "...", args: [...]}, for special formatting + (Ox.formatType(args) will be called) + sort: If true, one can sort results by this key + sortOperator: sort operator (+, -), in case it differs from the + default for the key's type (+ for strings, - for numbers) + sortType: special sort type (title, person) + value: {key: "...", type: "..."}, for keys that are derived + from other keys (like number of actors), or "capability" + */ + "itemKeys": [ + { + "id": "*", + "title": "All", + "type": "text", + "find": true + }, + { + "id": "title", + "title": "Title", + "type": "string", + "autocomplete": true, + "autocompleteSortKey": "timesaccessed", + "columnRequired": true, + "columnWidth": 180, + "find": true, + "sort": true, + "sortType": "title" + }, + { + "id": "source", + "title": "Source", + "type": "string", + "autocomplete": true, + "description": true, + "columnWidth": 180, + "filter": true, + "find": true, + "sort": true + }, + { + "id": "project", + "title": "Project", + "type": "string", + "autocomplete": true, + "description": true, + "columnWidth": 120, + "filter": true, + "find": true, + "sort": true + }, + { + "id": "topic", + "title": "Topic", + "type": ["string"], + "autocomplete": true, + "columnWidth": 180, + "filter": true, + "find": true, + "sort": true + }, + { + "id": "name", + "title": "People", + "type": ["string"], + "autocomplete": true, + "find": true + }, + { + "id": "director", + "title": "Director", + "type": ["string"], + "autocomplete": true, + "columnRequired": true, + "columnWidth": 180, + "sort": true, + "sortType": "person" + }, + { + "id": "cinematographer", + "title": "Cinematographer", + "type": ["string"], + "autocomplete": true, + "columnWidth": 180, + "sort": true, + "sortType": "person" + }, + { + "id": "featuring", + "title": "Featuring", + "type": ["string"], + "autocomplete": true, + "columnRequired": true, + "columnWidth": 180, + "filter": true, + "sort": true, + "sortType": "person" + }, + { + "id": "language", + "title": "Language", + "type": ["string"], + "autocomplete": true, + "columnWidth": 120, + "filter": true, + "find": true, + "sort": true + }, + { + "id": "location", + "title": "Location", + "type": "string", + "autocomplete": true, + "columnWidth": 180, + "filter": true, + "find": true, + "sort": true + }, + { + "id": "date", + "title": "Date", + "type": "string", + "columnWidth": 120, + //"format": {"type": "date", "args": ["%a, %b %e, %Y"]}, + "sort": true + }, + { + "id": "summary", + "title": "Summary", + "type": "text", + "find": true + }, + { + "id": "id", + "title": "ID", + "type": "string", + "columnWidth": 90, + "sort": true + }, + { + "id": "annotations", + "title": "Annotations", + "type": "string", + "find": true + }, + { + "id": "places", + "title": "Places", + "type": "layer", + "find": true + }, + { + "id": "events", + "title": "Events", + "type": "layer", + "find": true + }, + { + "id": "keywords", + "title": "Keywords", + "type": "layer", + "find": true + }, + { + "id": "descriptions", + "title": "Descriptions", + "type": "layer", + "find": true + }, + { + "id": "transcripts", + "title": "Transcripts", + "type": "layer", + "find": true + }, + { + "id": "numberofannotations", + "title": "Annotations", + "type": "integer", + "columnWidth": 60, + "sort": true + }, + { + "id": "duration", + "title": "Duration", + "type": "float", + "columnWidth": 90, + "format": {"type": "duration", "args": [0, "short"]}, + "sort": true + }, + { + "id": "resolution", + "title": "Resolution", + "type": ["integer"], + "columnWidth": 90, + "format": {"type": "resolution", "args": ["px"]}, + "sort": true + }, + { + "id": "aspectratio", + "title": "Aspect Ratio", + "type": "float", + "columnWidth": 90, + "format": {"type": "unit", "args": [":1"]}, + "sort": true + }, + { + "id": "pixels", + "title": "Pixels", + "type": "integer", + "columnWidth": 90, + "format": {"type": "value", "args": ["px"]}, + "sort": true + }, + { + "id": "hue", + "title": "Hue", + "type": "hue", + "columnWidth": 90, + "format": {"type": "color", "args": ["hue"]}, + "sort": true, + "sortOperator": "+" + }, + { + "id": "saturation", + "title": "Saturation", + "type": "float", + "columnWidth": 90, + "format": {"type": "color", "args": ["saturation"]}, + "sort": true + }, + { + "id": "lightness", + "title": "Lightness", + "type": "float", + "columnWidth": 90, + "format": {"type": "color", "args": ["lightness"]}, + "sort": true + }, + { + "id": "volume", + "title": "Volume", + "type": "float", + "columnWidth": 60, + "sort": true + }, + { + "id": "numberofcuts", + "title": "Number of Cuts", + "type": "integer", + "columnWidth": 60, + "format": {"type": "number", "args": []}, + "sort": true, + "value": {"key": "cuts", "type": "length"} + }, + { + "id": "cutsperminute", + "title": "Cuts per Minute", + "type": "float", + "columnWidth": 60, + "format": {"type": "number", "args": [3]}, + "sort": true, + "value": {"key": "cuts", "type": "lengthperminute"} + }, + { + "id": "words", + "title": "Number of Words", + "type": "integer", + "columnWidth": 60, + "format": {"type": "number", "args": []}, + "sort": true, + "value": {"layer": "subtitles", "type": "words"} + }, + { + "id": "wordsperminute", + "title": "Words per Minute", + "type": "float", + "columnWidth": 60, + "format": {"type": "number", "args": [3]}, + "sort": true, + "value": {"layer": "subtitles", "type": "wordsperminute"} + }, + { + "id": "size", + "title": "Size", + "type": "integer", + "capability": "canSeeFiles", + "columnWidth": 60, + "format": {"type": "value", "args": ["B"]}, + "sort": true + }, + { + "id": "bitrate", + "title": "Bitrate", + "type": "integer", + "columnWidth": 60, + "format": {"type": "unit", "args": ["kbps"]}, + "sort": true + }, + { + "id": "numberoffiles", + "title": "Number of Files", + "type": "integer", + "capability": "canSeeFiles", + "columnWidth": 60, + "sort": true, + "value": {"key": "files", "type": "length"} + }, + { + "id": "user", + "title": "User", + "type": "string", + "capability": "canSeeFiles", + "find": true + }, + { + "id": "filename", + "title": "Filename", + "type": ["string"], + "capability": "canSeeFiles", + "find": true + }, + { + "id": "created", + "title": "Date Created", + "type": "date", + "columnWidth": 120, + "format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]}, + "sort": true + }, + { + "id": "modified", + "title": "Last Modified", + "type": "date", + "columnWidth": 90, + "format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]}, + "sort": true + }, + { + "id": "accessed", + "title": "Last Accessed", + "type": "date", + "capability": "canSeeAccessed", + "columnWidth": 90, + "format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]}, + "sort": true + }, + { + "id": "timesaccessed", + "title": "Times Accessed", + "type": "integer", + "capability": "canSeeAccessed", + "columnWidth": 60, + "format": {"type": "date", "args": ["%Y-%m-%d %H:%M:%S"]}, + "sort": true + }, + { + "id": "license", + "title": "License", + "type": ["string"], + "columnWidth": 120, + "filter": true, + "sort": true + }, + { + "id": "rightslevel", + "title": "Rights Level", + "type": "enum", + "columnWidth": 90, + "format": {"type": "ColorLevel", "args": [ + ["Public", "Restricted", "Private"] + ]}, + "sort": true, + "sortOperator": "+", + "values": ["Public", "Restricted", "Private", "Unknown"] + }, + { + "id": "random", + "title": "Random", + "type": "integer", + "sort": true + } + ], + "itemName": { + "singular": "Video", + "plural": "Videos" + }, + "itemViews": [ + {"id": "info", "title": "Info"}, + {"id": "player", "title": "Player"}, + {"id": "editor", "title": "Editor"}, + {"id": "timeline", "title": "Timeline"}, + {"id": "clips", "title": "Clips"}, + {"id": "map", "title": "Map"}, + {"id": "calendar", "title": "Calendar"}, + {"id": "data", "title": "Data"}, + {"id": "files", "title": "Files"} + ], + "layers": [ + { + "id": "places", + "title": "Places", + "canAddAnnotations": {"member": true, "staff": true, "admin": true}, + "item": "Place", + "overlap": true, + "type": "place" + }, + { + "id": "events", + "title": "Events", + "canAddAnnotations": {"member": true, "staff": true, "admin": true}, + "item": "Event", + "overlap": true, + "type": "event" + }, + { + "id": "keywords", + "title": "Keywords", + "canAddAnnotations": {"member": true, "staff": true, "admin": true}, + "item": "Keyword", + "overlap": true, + "type": "string" + }, + { + "id": "descriptions", + "title": "Descriptions", + "canAddAnnotations": {"member": true, "staff": true, "admin": true}, + "item": "Description", + "showInfo": true, + "type": "text" + }, + { + "id": "transcripts", + "title": "Transcripts", + "canAddAnnotations": {"member": true, "staff": true, "admin": true}, + "item": "Transcript", + "showInfo": true, + "type": "text" + } + ], + "listViews": [ + {"id": "list", "title": "as List"}, + {"id": "grid", "title": "as Grid"}, + //{"id": "info", "title": "with Info"}, + {"id": "timelines", "title": "with Timelines"}, + {"id": "clips", "title": "with Clips"}, + //{"id": "maps", "title": "with Maps"}, + //{"id": "calendars", "title": "with Calendars"}, + {"id": "clip", "title": "as Clips"}, + //{"id": "video", "title": "as Video"}, + {"id": "map", "title": "on Map"}, + {"id": "calendar", "title": "on Calendar"} + ], + "media": { + "importPosters": false, + "importFrames": false + }, + "personalLists": [ + {"title": "Favorites"} + ], + "rightsLevel": {"member": 2, "staff": 2, "admin": 2}, + "rightsLevels": [ + {"name": "Public", "color": [128, 255, 128]}, + {"name": "Restricted", "color": [255, 192, 128]}, + {"name": "Private", "color": [255, 128, 128]} + ], + "sendReferrer": true, + "site": { + "description": "pan.do/ra is a free, open source media archive. It allows you to manage large, decentralized collections of video, to collaboratively create metadata and time-based annotations, and to serve your archive as a cutting-edge web application.", + "email": { + // E-mail address in contact form (to) + "contact": "system@pandora.local", + "footer": "-- \npan.do/ra - http://pan.do/ra", + "prefix": "pan.do/ra News -", + // E-mail address uses by the system (from) + "system": "system@pandora.local" + }, + "id": "pandora", + "name": "pan.do/ra", + "url": "pandora.local", + "videoprefix": "" + }, + "sitePages": [ + {"id": "about", "title": "About"}, + {"id": "news", "title": "News"}, + //{"id": "tour", "title": "Take a Tour"}, + {"id": "faq", "title": "Frequently Asked Questions"}, + {"id": "terms", "title": "Terms of Service"}, + {"id": "license", "title": "License"}, + {"id": "contact", "title": "Contact"} + ], + "timelines": [ + {"id": "antialias", "title": "Anti-Alias"}, + {"id": "slitscan", "title": "Slit-Scan"}, + {"id": "keyframes", "title": "Key Frames"}, + {"id": "audio", "title": "Audio"} + ], + "totals": [ + {"id": "items"}, + {"id": "files", "admin": true}, + {"id": "duration", "admin": true}, + {"id": "size", "admin": true}, + {"id": "pixels"} + ], + "tv": { + "showLogo": false + }, + "user": { + "level": "guest", + "ui": { + "annotationsCalendarSize": 128, + "annotationsFont": "small", + "annotationsMapSize": 128, + "annotationsRange": "position", + "annotationsSize": 256, + "annotationsSort": "position", + "clipsColumns": 2, + "columns": { + "Colors": { + "columns": ["title", "source", "project", "language", "hue", "saturation", "brightness"], + "columnWidth": {} + } + }, + "filters": [ + {"id": "source", "sort": [{"key": "name", "operator": "+"}]}, + {"id": "project", "sort": [{"key": "name", "operator": "+"}]}, + {"id": "topic", "sort": [{"key": "items", "operator": "-"}]}, + {"id": "name", "sort": [{"key": "items", "operator": "-"}]}, + {"id": "keywords", "sort": [{"key": "items", "operator": "-"}]}, + {"id": "places", "sort": [{"key": "items", "operator": "-"}]} + ], + "filtersSize": 176, + "find": {"conditions": [], "operator": "&"}, + "followPlayer": true, + "icons": "frames", + "infoIconSize": 256, + "item": "", + "itemFind": "", + "itemSort": [{"key": "position", "operator": "+"}], + "itemView": "info", + "listColumns": ["title", "source", "project", "topic", "language", "duration"], + "listColumnWidth": {}, + "listSelection": [], + "listSort": [{"key": "title", "operator": "+"}], + "listView": "grid", + "lists": {}, + "mapFind": "", + "mapSelection": "", + "page": "", + "section": "items", + "showAnnotations": true, + "showAnnotationsCalendar": true, + "showAnnotationsMap": true, + "showBrowser": true, + "showCalendarControls": true, // fixme: should be false + "showFilters": true, + "showFlags": false, + "showHome": true, + "showIconBrowser": false, + "showInfo": true, + "showLayers": { + "places": false, + "events": false, + "keywords": true, + "descriptions": true, + "transcripts": true + }, + "showMapControls": false, + "showMapLabels": false, + "showFolder": { + "items": { + "personal": true, + "favorite": true, + "featured": true, + "volumes": true + } + }, + "showSidebar": true, + "showSitePosters": false, + "showTimeline": true, + "sidebarSize": 256, + "theme": "classic", + "videoMuted": false, + "videoPoints": {}, + "videoResolution": 240, + "videoScale": "fit", + "videoSize": "large", + "videoTimeline": "antialias", + "videoView": "player", + "videoVolume": 1 + }, + "username": "", + "volumes": [] + }, + "userLevels": ["guest", "member", "staff", "admin"], + "video": { + "download": true, + "formats": ["webm"], + "previewRatio": 1.3333333333, + //supported resolutions are + //1080, 720, 480, 432, 360, 288, 240, 144, 96 + "resolutions": [480, 240, 96] + } +} diff --git a/pandora/manage.py b/pandora/manage.py index b77df77c..0d7eb6f7 100755 --- a/pandora/manage.py +++ b/pandora/manage.py @@ -18,4 +18,8 @@ except ImportError: sys.exit(1) if __name__ == "__main__": + if not os.path.exists(settings.SITE_CONFIG): + import sys + sys.stderr.write("Error: Can't find '%s'.\nBefore you run pan.do/ra you must create it\n" % settings.SITE_CONFIG) + sys.exit(1) execute_manager(settings) diff --git a/pandora/settings.py b/pandora/settings.py index 7b9f7a26..2834b8eb 100644 --- a/pandora/settings.py +++ b/pandora/settings.py @@ -162,9 +162,10 @@ XSENDFILE = False #with nginx X-Accel-Redirect set this to True XACCELREDIRECT = False -SITE_CONFIG = join(PROJECT_ROOT, 'pandora.jsonc') +SITE_CONFIG = join(PROJECT_ROOT, 'config.jsonc') + #used if CONFIG['video']['download'] is set -TRACKER_URL="http://url2torrent.net:6970/announce" +TRACKER_URL="udp://tracker.openbittorrent.com:80" DATA_SERVICE = '' POSTER_PRECEDENCE = () @@ -178,10 +179,10 @@ VIDEO_PREFIX='' SCRIPT_ROOT = normpath(join(PROJECT_ROOT, '..', 'scripts')) #change script to customize -ITEM_POSTER = join(SCRIPT_ROOT, 'poster') +ITEM_POSTER = join(SCRIPT_ROOT, 'poster.py') #ITEM_POSTER = join(SCRIPT_ROOT, 'padma_poster') -ITEM_ICON = join(SCRIPT_ROOT, 'item_icon') -LIST_ICON = join(SCRIPT_ROOT, 'list_icon') +ITEM_ICON = join(SCRIPT_ROOT, 'item_icon.py') +LIST_ICON = join(SCRIPT_ROOT, 'list_icon.py') DB_GIN_TRGM = False diff --git a/pandora/templates/api.html b/pandora/templates/api.html index f5d28553..b511859e 100644 --- a/pandora/templates/api.html +++ b/pandora/templates/api.html @@ -4,7 +4,7 @@
CmBr$w2^^gVXKDhhLzlq-&3*iM{k7nIO`x|7 zIk~%$u~^bQdYLo =^uae;A-^0IGQZL+!xb(g_*H}4*f;vY`P20cp-{hIszvZWM`;~I*MD1ATTFE(B z@DI^|Dz5)AQBVgPvh*BbsHejWrDq*90sMdjTh~x3V4t6WJ_rs7S4hh&PSAJMR*3FX zHsC8Haex21Erc8^@moB_B% p1+oPJV<`OgHY8X)zq1u~hrc37 zsD!4dNd`PNaHs6b+1d1cVacf2wYp$D*%e0hH+?jw#0LLw(GwFmibdbQzZ)Bif8DZB zQbIPA-Tcn5s8(3`V^lY3oaM3%N(ga1lm1&QMdg( <#KV{}yK@)GAT2(=iYC%CzG z{%=oIoZqQG*$`x(_|?^O23+7XCgr88CtHJl{-04E`h6f=!XIC)NGehWVQmJ K(o zpN| $AP>2h;3Eei`i3k&wm%Tm-<>J_{; zPgK}T7FCFXtgDNT-p)6BuP{yC8>lhCXYxZaq|eNnYO_HJ0Ub==-Tz+y;s-Dvm`lyh z4qrt@s?H+ez5J3@c$gC64CGqkc)Q)dR!)qPk&!RyWb1*Y#0@Q8B^iI?H#8Vke%SZ> zr-bRa5$JQcc$BZt{2&KA5tPe{>n9Jb2*1 z;y`fi*@Fadbm1;EN{IU=6hA+9Sim-Uv6f|{v0PB_gNBtAY-4C%4ct$-xVZV0QkR&s zsBdYh0H6_|w+U=GjUHs^K+ALlEfT;*XJ#oyBvZRV{D#9R)kZ<6)?A#?c+s#1+@TBo z2b`Z!gYnQfPUMUMLmpQa?JG|#gHVOPg-wWT7U=E(`6M>a`D>wGB49m0#C )!Au5 z9H3+nXT7qAI#0FR=#hE?AiiG!rHI&PWu_%Nd$uIdP7>8sn B7X?D|V1%4s$1LHw%s1#|Uv5t?`H& rM&-dcl*W9KpN|2P!AXO`??_Lr%+}5<4P_ z8IO@nEzLjo*?G9-5~mkM{>izRe8`qJ2%dAUQMmZhU0MZOF4YtUZztz*GkSh$9qoy| z+68hr!s97Dvk1fJ`zqFoUu3y IGVBn zM7D*cgl5gWe0<^E6uH?L4QE2BT+-BM bID+&|Z_QavE(UAX-gDHyV?111_wB#7^&U2*@*zw5;`_j)Gu z6Rr`Y4#s5yxNcg(5ppdsdEKBq&?`v<8H>$9YNOx{0YQE;I~_4L{-ZCIscM z>B1OTX*vA*8uoJmaERFf+$FB16-B}knRgHwti>$!kLU SWrSWN_$B3-4k0il}O zyr_gg+eSu}s-lq7 L3*Z`(S)k|Iau`)H4#e)TF!{#v(Bd`);LjlI04ND=@({z6(3lo9 zwbJr{a4RNe5{9_5dOX(KFA$cQM$PCE&_fd5Dq@FvryUe%!_p7OeJ$p?{qm?c34m_sZ?>knH;n8}6dI9?js4n0otY z)D8T&L|MQ|sM;^#0-_HN1?ygf0rvjiXKX#m-H&=ikQUlgCe(#Nq#{3{bKmPyM#mmU zA)ZeRDc@HMHc?#%d$xYM!*^S|ybjh(;JSg4sBY&P&a;dN s^W5VOpIS+)BRWl&98(&zdr800|`#E z$da*f&G5-7br_x`?oU_<
Lu zu4f_*=!}5BWcK_v!kM3E|6cn<6>$fYqr4sN``c$U48x_U@>rs%8gLQhGb(gNm9cNY zbdjKn=a!X407Nx2`+mtna5LuoAA^2itv4Np^k8`C#B~sTzIi<`v}776Zs{4{0QOM_ ztIy9jd;P0ib7W`-f8ccSjqcqI=ivh{^wf_FWWZ_Rz`Z~sU!M7~_PsV4$YtbaNnr`j zuZW5<32e{~;_cA(?dL6tV9}WO0 {0G$&6Hq0LYlVbl(uy>&~}UAMYNf0c0Zh)i1qK$v#TUYK-@sb zwtMZeH~i#Lk1nW#y1M=Kp|7}Jy^2cA%Q*Nuo{(O1{SFx#CvppNkTt_rC7R+S`RPGe zYI!*cLujDF^t8&srH!sHI4worV-gx#lacWntX74K8;l##uAu9F yok=l zkVTx*pvQ1|DRy!~R^>WL>!{@pT7n{&H$LE!TUz?V_q@}C!ka2c>?QK!Kd oZP#uPLRa) z78ZzwT*1!U!SVP1k*K7gEuI~p7+pho&g?~ir5OOC{9m{6lGajPxB}W^URP@h>F~(` zr7$wB0b+nrB3zaP1WCMZ8K$hSEE&C^bHXJ-byX|iIwT+J!mZHcv5l ZM*Me&(oY eCOW&mgFyj1WRV>jx!@ux1Zk=1sg^5SNBke7{XW0| literal 0 HcmV?d00001 diff --git a/static/png/pandora/icon256.png b/static/png/icon.pandora.png similarity index 100% rename from static/png/pandora/icon256.png rename to static/png/icon.pandora.png diff --git a/static/png/logo.0xdb.org b/static/png/logo.0xdb.org new file mode 100644 index 0000000000000000000000000000000000000000..ce3e720202f297d30b9e14d5e85dd58941e10f15 GIT binary patch literal 32274 zcmXtgcRZEv|NpUf4#}38y_1=BB72j0Y(+x0>~wJKGP6h7n~ +#6B?`yqY&vl(BU2PRIVtQf-1VW~!s-zErV1vJ6K?w1|AAN5}o oqEZ%Pasxc8$-=uk+_}E=QL05Hs%GSIHwc(EK4_dtO^eUj-3az+;_`nw zePC-jFcf(jaxkBXln|NN(-NAveYc@?Zl+{1b)Z#1kD7-lS?Md4nq$6dsLYcv2(f}o zdP@3f4kq$DA{xr=S~7(DDujX%f@e6F=_`Ah JhC(XaT9a%W zTIZF6Gk>+jC#cCu@7pkG(jLcDhh{8_BnXrMatD$Oy{pIqCB*&Fg#}@NL?hm|ZMEDl za ^Qp?WyQq`WoN^;yqpO|z+W>Ups*5D<4}m6 z9u-fzW|dB%{%N6pX?BFo><1^a1P(O;1T-6p9sG=yt)ixbf)E#5@e34|=P^>j$lKoD zV;ifLI=HGhA+|8Q>fyHP(Y$gVh|XG(tk~S4WW|NP!%_Gnk)=ukK|qNixD+(>l6Mn| z@23w9Ol%x+ZEkL!l$BkGsF%hgzerWj?^EzHh1TRrn)uDD$~P&1QDF}()Lg%L2y>`r zLF2=C@YH$yA~gmZcTU&V*R3)$GXr=shdV6!sn@T_*%7Mq&w^APpm*4sDJWqa)dZ3d zg@<=_#KgoNj!#XUh1-js6DjMpKq+YLI=uR^LNC{_Fm~N#oj1?f Vti--BfN(Q z f?5h#M?-FRK*7(es6ojRaik_Z+*Tu={ z{CQB&b(*2k+*heni69PyaLJ1kl5sk=u;O4--6b$*uN7X(@HaFWI;BgosUx6lYLvR_ zU5z{U>+9 zmORV4dD-sN+y$QKNicbpE}!w>LLF zF7Enkqf2PC6Quiyf=~mp?{XXq#V&tR>0eM^f0!VqH3L3qM2Zsj`BZ-SlMr~HuoR>l zTVAq07+dn-s_|nL70kr`{{G=SmPuH2r}<(NKK6ozthYQ4#PRv_mXP6$2N)I6^uTd% z_+pml9&to99kem<=6xzu%T7g<{vZjH^?3(H{~Vs@TDEv?VA?!Zzl-dJe9Rlhe!Wwb zL&Jdwp^oVrXApmJ_Cb^LP(VqH)(#zIMO-EaW}5|yfIL#-Rin&L&bWX7eu&4(_HT?K zZ^~NUvOLs8AzuME^!c}^lVwH;zTV!~wPj_@o{R*GI0JJ3O~@{BV5Fk5-WRaZZicZ_ zsF8LHf?G^h(hOOYSLLVHaGm{pfcb7Oy6|uNs%rl)4OopXI4K+3hve^x9E#$Z*Gc!E zja*^W3-#p)S@Tje_rg9{85m3iF-W*x?Uj}7dND>`DyBR)X}p$dEVP|vm0z+%(Jzr{7rJVlX5G(AVKaM|P9kR2t zW46u@*D)=1DU|v#TVV}Q2sh-VvfkfcmF5^b>1^}@qSET-Nnu+CI}E}S5~sO3IS &vl-SxG22(r5Nmod!wnXJiM?F&S-R!h3CSo?WGzU{rt;5K@ zP7HdFnmAl4BmrIpOLZ|gVWOstQd)06oxjZDA8fTidGi$3eV(}464+hGrSn2k>s9=N zbz3dhdu@j!2H`LpFR5&tGzXOvzGU^nUv6_P$HjI0Zck&00{%OTxJ_7WEr}f8s tYce Aiag!Ry1llGi- zrwW*(*;#uNnmi3=e=-CXcRq0bb>#zx&vkk;MpD42cmMY`EF}U+LIm`;JL3~$<5M?O zh3Vl;(=wG`4=>KbkG%YRo6EJ~Oe=4=#L{mjB`@axW+{w-g2KU6FiYBdb16MN{dJ~0 zaT_aH==u>sgbj!Ii>ZC%ti$AW&;NU)?$>@4I0jp&Y7AeGFlCO s*Mg(Xrg mT_9hCC{P?6weEFX z>XsVv`mqL7Z4`#IZv{|6rZy+bG3^E1nGdTmt(;RjlS-@*W_u^w*txH3 P z9c*gj-55i6iK==kigfUZC)JkMdpg MHaJgSbI}AzOf>>qm*n@p#@l>_z+%;IZjf@3m9@k@)jtD(lF#s ziz}$p$@*_Oi0n=A)%l{SDqapWmRqleBFE ePKns4~Ma?wp1w**3#LDSefigr0`-b#V?3GvBprU^YEn&Mr~|tJV@48P@;h_ zLTaEtc&LcUvOEbWc fgo?;es90j+Uikh|W{DQ k-B)Shi6Zo!=ZGB~4#%)&4x{T6#$O7Y?;FL3 =VhAtbWmJ;R$PD` z0 f^I@Ai?2l2$A*N414uV#1eB>96U2IJzc2(O#p1w(o!B# z5s}v`D=RO&yu?H#B#P4p9kf6RHk!n<+0&-PhrZ*Bh6!1=o%K#lO_88H2rcw+LXjoh z(X+pPope@ +h$3v){BgjTh-RT)?%? zSWC=Z#hg(nvq3^vBzrGSu^~~=g2B417fw#EM@F=9L!}4YZ#Gdq=%nYT3n5mWFPC7_ zX@mFfsp&t^!r9AN7i~f+)QBOVACs|J?fZs;_Uu(HP?f=SSiEvF^Zl=Nf7ot9Aj$f& zDe8)xNm%Mm6K}}n9A)AS-UE;-6M_!h?G7i|-u_+h4ZsM`NHbU`w@Aw$Vdv^wR#qCV zC2m-4=;Kcka%HD?jr6F#+KX &ijR#oSs%&nA)yfzTk~i> z%l6r4LqP0Er>CdUr{F)d{gE6=WcV6+cRY=%rKLr#^Z8^MbJfvAXXn98BN^C3_M%d) zpuO{*&U%uY)<4yfPEns^Em)Gg^|`8*#YWHvV)3*}S_=wO(4*3&3U?nN`Tas%MpDx8 zu8a@=tBvu(*cC~4IBM^G7Y~vXO?1GpaEAY4`V*1ngarjx@5~q9x12_YcR|rHGNOQh z4N~=TsT*(WXQ{!+#ze6J7&o1=9+i>NYiM18cAT#bCfKn(nSCOC^RtHnK%1Mc9L+x%&@NFS zIT{kOB9u0`*itp| Sg(}M-4bKk$` zZo5928Ijp*`w*Lu&;&X@9Ch8K+!2x%N&(|Qd0PdnXZxN6n1z;$H
rHaaL%~=0|M5Y+!k^~Nk0rXy7V)ULb zd^R7T*OxYB4{&ojBb_tWen#Eqn*E@XAWb7=`Qjd;S4_+N-L$b430TbV0ZvZN;ARt& znll^j!sHv^rgGqS<(YMO$NS<`qF28Zv9q(2cK6kvcg-#{jSuJ|emu`8`U79T)cyFD zyGKf;#L+>)X!76H)m3wvrN7$7-kw{7iws>%-;+OIsFcWS6$8a4! a=U+PQM=_cMocIE;Q{m%Cv>A)%mlE&R#a;_Exv%e^28532_VsXq65` md{FRKKo}F!5k_b7JC>D2{58QYre`)$iJkuq1V1@6w zHF1GT!(PlYKC)WzcMghCCcG&Sh}kYcJ!`wWyHUoLuU2;+Mc#5@L2wuv#brflAMKGX zoeELMD8qPM=9}ECRsbHZUCPB=t%o =CK8 z!}Bu$sZp4%kn8I6pwl^t_k*jUhJ>y0VdjwlB35nLmKv&qVS27KbsSgzZg56f@>0j3 zy$`snn|^xYTjXjNGvl3Ek5(fka>U+i7k4g}im{cW_V?ClVw8Orf==5{huwk=OxyjB z*I@a17g&P3Y}7;8;jsLJ6=uxY+5+Z!A@`fTXiusGg<4|4LvFk<8DbdWI}ReyBTLF! zE}DY3n&{;Z(?{Ek1=9}ay~RYBjfFc-0DKu&Vyiv2LJx+N?##9N))?~s&Us6-ieAm~ zR4tycf`FGw%P%0dt~KFjN*IvecvUmbU54Z>3giIoe0OJuDQL5_SQJcJy9xgVtHRzB zkxVG;WpJ=1yy5v|YRI7*didL3;JOSuSnVx!&LqBW?R!Z44*$;=8ZSMeurJ RO`b}iVhrzvU@m36;5rIk)nWSm zw7n|_I$(LcN)0n`Ag~}K1<~|b_Cb$g8bWrD0mqQTCjRpMp9VVZ=is*ofO7iuPtEyk zc2X-Pa)2V>4PUr8t`3>|3|h~EAa#sI2{T@p^??ooRMCz3`FS!O+2fxTP8soIxU+On z7~wi&OrPy_N6=-X{P9l%8qJAMZO#RH+g8y??m>@d;L$j`A~?EMW@?4{xkn$nzQS#f zR|3-pSH}%>WC5#+WS-wG{0-Ohy?7I ~*9U^phq-Q= zh5GtqdN|4$K_Rr624R
yWpgPgVr 99wcMq@twCK`Dd9dx_(i z;gb+zOWNuKxl=6PPpfC-ed26&X{M%^EROPK-!i4}zIsRmR5X**-rG9%S`+rk`ae>E z$m?e&1$v2q)%kbhr}xw{4jF4dd{QHHrRWT~9o0KM8iGC)UvI;ukpARvbD%i!DS*3> z#W^EYpQ>=66T|F-hsA4*{=QmwoKhX=&{kOJ;8|;;q?U*n#!xFN?f*=e9*{>LG *i-J;Ka-wZ5;oRI@`Q6~NB@LmreHdIcom4C)@9)C2?qsL^ zV5&g59Qq(Zr|sW&zVf7R98pM~P| AD%Hka@#qulYL)QDvv%}*<4&)Tm^*qgyrJ&aei_mfPgvl5w-F9 zW#{pvtSAyXJC1j%nzHAAl4iZ<@NG`5fEd`@=rUw4TFxADb-**z$ds=~K^QjBS(v2B zc|Sq+#N@T7YdD;%>)+D7#V#eb9~V=02`OwpJ9}>{JaF49F7F~CVDuTjlAI1W-5vJv z^7;hW``5F3vpk@^^x5jYT6|4lCgC#e^=r`a4q2@sOUYEX+cBRgR|P{?44=@P??O;% zPEJl#tG(d7$3m4@*+r9}?e$=uhqFxsC~N*#CI>d!cVUDksmjvQ(vNSZm@B5QV5t=7 zO*n>4THZlblTR&=y2JNk7HHUTDc AokU2jQ6S~+y@Uyh=v zjH{c_0o?^)6vn9X0Eh%*?sC5qDY0VNbv}k0e-t7 z-V}9%Chh|R2$H=Zf2*Z(tAz|gq?RbTZs7}0`&_jpGkt#k$W>#?MR=7HbCYHnf{TlG{28Vc_bsk7@X<@&gx4D-p(bTHk3 z{^ 5^QDzR1K0=Z=1s0UVp;}qx>IIH>>T>)Q3c#U42Ht^ z``;sww;)sduXDo`Z>$gDe#3rRcgAO3A{>;PvFh-5(8Or~UvB_C8(V)*Rr2C4!t+*y z&6lyUw^vOemrZI~6Rr++K>G+6;RNQ<@I6W*1C)1DL&F2q)Rd~Znc2_&{>K8qkda(B zYqr22X|7Hr^VFu?OVys+oog)y^3*CNEV)LaYW0u!B@LDWD?&BPb9!t{xY#K}iO0y? z+`Kq5Bg5~}qc^{%rntcqHWE4d;HVUr8OxWAi2X~HmGb~tW(_1QNv#~jve62T%5+dm z6ty82AC-!e-T#79|IOZ`A#Gk_JC0In9T-twa02yG7t>$cfBx7Xj2CK@f~q?U MsoTS)PEr$SwT*X%8yk=i%XDf6k<+@-q4+7jwWRD8nFyR6tPzwIdzCtEZ#6 z^2{hPGH369P{Jw#J5A8vH=aM(8(u6&fG%DiQW)D1+m)DT%Oylru zjm@JW;n>F$V)t~QCc6X8HdoCzs&hnDRn^A9!GXAu(cb4~Kbh;T)1AH;8r1n70DHc- zll9FLIY6r_1j5xU&?30RU8ZpXU>UCCM`aeNbyFX47wQV8eQWXFGLx2(IRqM0E%#A1 zhZ
;wP_tyv zqcSun#Gti$i5!@nW1~@=tV79lMtf6nvYwim>FG5IxA})1uMXFgR8&-^z2`vBBTd$? zXjH=|Vxt_9QcX<$-4PVv?&ydFY`2FRA8(2Bm$!MyEl^03$7gtR^`pzmumfurx3WCn zzkiP;CnxU#^l}&gkLHAstCcrkz*ZlswzP_z5{1`4Y_+`A =KVed9{ttx_4E7O T@ zXoaq;3A#L4nxD5E|NXmpq^^S2Zo84D%{&~6z3P$vVy?w#&|b8@&$d=E@#EM%1I5p{ zK{|o%uLgB@6Elkoxc|CT^q-x5J{Y<`Dt3H&YISu~ET6rzv0-Y&n_`Rp#{T#GZ}?lI z0DphV1HZN}v>NByV}tNt#Cr?(E*~dyymg?%QdrJHS$%$)j|Re!ww2XFZ}s(bb#@B? zqW2qJnp&!2nzkJMHBzc@o09gL*YEoO9m_ul&_zg1-$afW{XSZ+MA8I7#EMAv-JO+E zArxV0<756x%l^%NoQ|1U9<$>XB7@nkA8oX3^K*|Y;PyW7V>C3X vIp-%P?ytjJNeczB z6)O*PY>|XzWwxT}$v``i2)vwG*bQ6?rvYGg_|@})FDMor+DL#FD_dIwEQb0rdwfSu zQ2KC`$lU}!AhVUD(R-NS;TyVQC77a4Bc{b}Q_+NgE=%45Ztc%%cx&UI^e~+20X8pU zhzIhH!hm~TZLRy$o#_U5A$J(}@nTjIpmZ-nLNKM6lLfT-GMK r~!# zp@$GitqCblVLG6?4ii>aeOAd_oSgF#q;DeA#S=YqrdquD)D!KaCps5hArwS*q+W0p zIO?|i`8OSL^eE h7AY={C+rFpB5OQJC~sR)f5g%z!?(H*;5lkLi;`DyeG zV^WKckI#wjv*oXNL{li%=rBl40Ljwjefgk{v4|;5pBR9-fB+c{4oX0GHyZb_7O;^> zCDifk4TAbXW9erDmWHQCc+4A)J@}C5+Hb7XFgi9q-t{b|^DHK6;j(=}rtax*1_5Ba zlo5w^%@z?OBO^+p>7O(T6Iq8KzCtmRpA<`xJ=`?bHgdMo*_=Q(*_1XiiUjmq=q)T? zmnLH6z01t_#qm~@4K1hLi{EZ*vo6 D+}aK&a?xH%MNT~G zPQ#v(tyLruHr4>^UJiC%4H|(;_3*=%^imc9@AX;N1N~A%58$~TQs`+B;VQx`P<@8H z%2vVWtMPUN@!1IjE81hlN-#n{`X5Smb{{q*wM&|A?{d)EDNTrd;xFIXD9+ubXJX3U zAHsTF+0;Ro2Rgyr^t9P07n!54q51KGLiBo0;PZRE;lx y2=$4QXRanFOKhxG?T!Nxj$dBAd%4p9H7oI zH$AKWeIUG6(8T!-VC46}CAJQa=!yfLMS&B&P>Q Crsf2blT*kFp+V1@!OQfD~6t)#2~ozaw(d zN5w__gHrq0$G9eBUT|Y4XXlvmvQ0#gtTGEBjuHnrZb03Udbt={RQ6no)PR ^*@ zFhU&GSRuYx5&D?EeIPPtN8Vz}kRbiQc=Repq{!FD=XoMW3hN& zv{hT!xKp%X2;9+!pbjQF)c?j;)`O9Z{%sMA>$BbeW$spagLIHpTeGK6ZV@hnpA-`- zsWllo?kU7K`piO)u=x-N`#mY#kF=o`78ZWX$iR@PXLJWUCuULd*2d E25neI(YvUqC_cB2 z^QXl&v_EqlS#N1`ARNJdE^7qs*A(Iy8ahPL!K}dcZcH>?a~A*v0C8ZsC1KX4+m{qa zvIkr3)Kr$#5-U4R3myM9bV8WQZ1a&6v<&91B2V^S>;%1T2#vJ z`1tsMz?Is_*h2W htC z5Io61Jjc#UhDm4NvLI393D+-84%urzrKeJU*PZ$}sIBH}f2l#Sg=uPKULi~^Q59$x zJQXr^*osv9xRCc58PRbG3Fc*G_`I=p_oUvAgEK?rSu5@rKdQ@3b!1SionmOTs#w`% zi5UmJ!_36PNKPz-E%v_A2sq_$wTlHtZ&4!Ydy_gAm-}Ky05;3!AB3jvgovQ?9tK@_ zU31ADM2WI;)Xe_Kk=Dft)n1?z XTa8Mt>+5QNpRea@ zsgk&8+*o~1{-e_vMFRN=n2AIKP(ZLDA0G-RXr1Cz;HDbTGcdTQJJC@z)J(G*-Ue3c zPhnE}kI7qVRxj-Baf4 (~)`6cL)mz zu|vh= C}7yfA?BLg*br9S_P?IS zUT`6H{Ir}O67m)ufz5K!dW~%tJfKCE3d7#9+2VH(le0pVu)!FEgXNq|pL`pfqDy9H zc{#$Gt|{G8mG)3YBp<(oB35Me-|C=d;zxz$!%LBu85tQ707U)rJR&U&c{GD1i)F1p z@hLF$bmT|9{3=gj(tKe2Z0~Z`V4*${5$o%Fu_b0RUP`30#tkff)HBcC9LbxOT?rq! z^1OC1 jPeGL9C6NgDv%asc(gYTg?;_|*Rk7s?#qiGIl5^O$O4) 7pU@`}$CtG@Cffbqf@>2CWtRdLbalr@q5hB0FjJxkfz7`T8z40*YPa z)DM&%H5C;C+*R7G@_eavGNj{K^_=VPf)IHSs$oS?5#uF!3ic2%cnAz0$*|$&G(M;Z z;(o#8Hzu+d#i0f^aR!J=wLGdhpCt^=Ook}TVo?&p5K#T1WNn0DAR~fYK}%7IZBTS* zXh@8P7*E!^vz!((8zcv@d4o*$6ja&3|3azM;sHWD*f^nKr=DXfVoV5#uIIx}zD&=Y zW6S6We>Mor{e*Z++tEb4CX=;7re3f010NxQ$VpYd0mbEO^(TP=NOU`Wg#sZfp1Kn? z#SD%xmQs-{j0BfTJch}j07f;9&*Hzs_BjCged d(7385|UZ0;uK5@zK_TCc%WPi<0rKrm4YA$A|C@ zoSS5iggRAXIMH(wRs;ns+6Kk$2%CT)^1~9!rbBoT+1gP2?#EX)SA2eojgc7m8RX~P ztHrw=luEK6K9Psh6R-@`UFUunBbrnyl+!DXT-;^nwG$fhR|?8*VJbM`xBR0_Z*O){ za#Sshb(79mtiq!J&{|{SlSMLa5(`v2G;AgUN_nz_7pt!RELZrSq~VH{8*zEyYwLT9 zeXCqRnXdu>uHSpE($WImYP*Zr#DPXk+ubXf=zbzQ@Aw;;!l4at62La0D}^{9H%MV` z%GpwNdC-8v7s7KT4ytr-wa%DJw-f;!6_q=G^mN+3Ft$C9uw1*rtXK&XOt?Hum+hJJ zZ_8xMn}hdKw?qztx{&?qaWf`_1B02xNL=!k8ye)VhJ1Z}2L@L~$u7_p1>C|9A3pR2 zUJ<}cI8l?qvb1(j+}zv%D1Fdc6{Bd$yO#B)$mtib`kn!xJ42=1d{0Z^=YS;?Aw@%j z>6mqh0X?Kz&Rxl{z0_>Nn?wEbEtnx!IxC|!No{me?$yU|q?Tf80?>v<)3dV$t`3+n zBB|3E=>W*Qda%tUZ?z0i>r!UE`eHN*u(226aJ^@MFROT(CDNMN~uKq5mYSDK-?Q} z&q<}MCoo8?? B99MVm&IBkd$Qa$`y`H76Jpn&_Ib9Fm4@LqdB3Xv6; zh!w!)4H{Nld(+2*bw4BT`m2^!SLgaLMk)|~dq~J?ZA$Is )I4?_(1Vw+9iu9blhCCn2ArEXdfisX!e zvJn`n99@w<#rkseoutqP9PnQJRa^7l%c!u5fw<%tB!G;JbabZIo8{HFZnQereBHM= zn&oO1sBlfri4JE})%_dAl>T-U?$X04&F={4OE?NxA8vBgG2Q?Z 2Or;tVZ=KqCaoT4uR#KfQJ>e9=$qnDID)QEJ?8^?Qom-YseTQqczf!Kqq1l{YrD zPjyG!5YVF+nA !h+d<2he& zUc!NLf`8G@H0A>#C4OMVJ7-i=3q{tc!&A5mT;G0gczfz9JYg&lQm{w>HDV6!BT>-J z{HyP Y75^&H*qtbcOP@Udq`-AZNt=$2Rznh!tt_vMOCtu&OKYn<(y4fWT z3VS#cG9nzS)n`k=Q;3^)VmDoHOgUKhG$#aoE)G+jZL*rz{;eqEBG5BcPGmEz+}YN5 zY*JOHjClgw%n_-*jSWQ@lHw&s8U!5gLg?EUaCm%0x^Y=d#DSqHOiD{js~Q&{uMKGB z-!CVO2cLlKfD(>{K{Dn2VFUZ|Ofr;3PWZ3zC?(gSF@0H03&8=l_VynwCq9j3Md60s z!S{dKlMdR&H%n-}bz3Um78NrCg9g4!nMEuVf;$*ZL(1iSw!f5ER#vZQ>CTEc8}Yl@ zNTctIObs&cvLYnodwllh;liXJdS(J~L}YL12q_>yYpZEi+I%T*Llmh5pcQ@(g#xGQ z95hOSLbB2#h)Yp>n*s<-AXN24Jah8u<(v(LzaRv&p7*^%P^OGz&GrtQ_IZnWU%grn zpjtA_f%{#2AR}CMdWegQD;?p C4D0nP))uiXi=2SVJ&2V3zgV5>v!p`-U6W?L0?_N Eh(i(;6)`zsq+Ox!0eli8=&M1@p?s~#6$UgPk N{0hcwxz49%U#~UN&za=;ak_#L_^l|el;s;_0!&~T=I0yr>-F8@w1Nqw+4oR z5UC2@WWjGGZgzY7#RvYu2R~jC;`-A6&%gjt>Hks{APNwdS3NA+KXkZ0%3dh;i4h_P zj`~kxC8ZD5)#QY%E38gfW-^V68QIv0QF-UFJml_`V(g*B99(+{6GWQGsNOF7v0PBmG(LW(ai} zvqM bopI(PlM3uPZoi#8$04H5I{lD6qrHg#(=bl`LTMqZF$P> z*#sW_P^q>TP4^CmVTHAv?%|n-0b5Fo8DvYmYj--SD}L5CWgQdx3*t9x&uv_9-Q#5M zvwin*V&fp>KnUIU6Qo8@sbc!d|E1vSF~bNY;|&;TY2We`imo$au?%e&f*Mf5)Y0Pq z;sw8~u<*-1VN&Wp)+$OIh~AT(nRQCowgc1=h(C$n?5nn?8wP9mHOR+K5