diff --git a/.bzrignore b/.bzrignore index b87a85b5d..fc966d883 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 ce631a3eb..0d6c37c4f 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 206887a2a..c8b7ca3ca 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 9f8c68e30..417464ecf 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 000000000..03ef91285 --- /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 b77df77ce..0d7eb6f7a 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 7b9f7a263..2834b8eb0 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 f5d285533..b511859e7 100644 --- a/pandora/templates/api.html +++ b/pandora/templates/api.html @@ -4,7 +4,7 @@