--- sreview-0.5.0.orig/.ci/setup-minio.pl +++ sreview-0.5.0/.ci/setup-minio.pl @@ -0,0 +1,16 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Net::Amazon::S3; +use Mojo::JSON qw/decode_json/; + +exit 0 unless exists($ENV{SREVIEWTEST_S3_CONFIG}); +exit 0 unless exists($ENV{SREVIEWTEST_BUCKET}); + +my $config = decode_json($ENV{SREVIEWTEST_S3_CONFIG}); + +my $s3 = Net::Amazon::S3->new($config->{default}); + +$s3->add_bucket({bucket => $ENV{SREVIEWTEST_BUCKET}}); --- sreview-0.5.0.orig/.gitlab-ci.yml +++ sreview-0.5.0/.gitlab-ci.yml @@ -0,0 +1,162 @@ +--- +stages: +- test +- deb +- docker-pre +- docker +- docker-post +#- dockertest +- release + +.build: &build + stage: test + services: + - postgres:9.6 + - name: minio/minio:latest + command: + - server + - /data + variables: + POSTGRES_DB: sreview + POSTGRES_USER: sreview + POSTGRES_PASSWORD: "" + SREVIEWTEST_DB: sreview;host=postgres;user=sreview + SREVIEWTEST_S3_CONFIG: '{"default":{"aws_access_key_id":"minioadmin","aws_secret_access_key":"minioadmin","secure":0,"host":"minio-minio:9000"}}' + SREVIEWTEST_BUCKET: 'test' + image: $CI_JOB_NAME + before_script: + - apt-get update; apt-get -y --no-install-recommends install inkscape ffmpeg bs1770gain + - cpanm ExtUtils::Depends + - cpanm --notest --installdeps . + - perl Makefile.PL + - perl .ci/setup-minio.pl + script: + - make TEST_VERBOSE=1 test + tags: + - docker + +perl:latest: + <<: *build + +perl:5.24: + <<: *build + +.deb: &deb + stage: deb + image: $CI_JOB_NAME + before_script: + - apt-get update; apt-get -y --no-install-recommends install devscripts equivs git + - mk-build-deps -r -i -t "apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends" + script: + - dpkg-buildpackage -us -uc -i -I.git + after_script: + - mkdir products + - dcmd mv ../*ges products/ + - git describe > products/git-version + artifacts: + paths: + - products + tags: + - docker + +debian:stable: + <<: *deb + +debian:unstable: + <<: *deb + +.docker: &docker + stage: docker + dependencies: + - debian:stable + services: + - docker:dind + image: docker:stable + tags: + - docker + before_script: + - export IMAGE_TAG=$CI_REGISTRY_IMAGE/${CI_JOB_NAME#*:}:$CI_COMMIT_REF_SLUG + - cp products/*deb dockerfiles/${CI_JOB_NAME#*:} + - docker info + - docker pull $IMAGE_TAG || true + script: + - gitdesc=$(cat products/git-version) + - cd dockerfiles/${CI_JOB_NAME#*:} + - sed -i -e "s/@git_describe@/$gitdesc/g" Dockerfile + - docker build --build-arg=ci_registry_image=$CI_REGISTRY_IMAGE --build-arg=ci_commit_ref_slug=$CI_COMMIT_REF_SLUG --cache-from $CI_REGISTRY_IMAGE/master:$CI_COMMIT_REF_SLUG --cache-from $IMAGE_TAG --pull -t $IMAGE_TAG . + after_script: + - export IMAGE_TAG=$CI_REGISTRY_IMAGE/${CI_JOB_NAME#*:}:$CI_COMMIT_REF_SLUG + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker push $IMAGE_TAG + +docker:common: + <<: *docker + stage: docker-pre + +docker:encoder: + <<: *docker + +docker:detect: + <<: *docker + +docker:master-kube: + <<: *docker + stage: docker-post + +docker:master: + <<: *docker + +docker:web: + <<: *docker + +.drelease: &drelease + stage: release + when: manual + services: + - docker:dind + image: docker:stable + tags: + - docker + script: + - docker pull $CI_REGISTRY_IMAGE/${CI_JOB_NAME#*:}:$CI_COMMIT_REF_SLUG + - docker tag $CI_REGISTRY_IMAGE/${CI_JOB_NAME#*:}:$CI_COMMIT_REF_SLUG $CI_REGISTRY_IMAGE/${CI_JOB_NAME#*:}:latest + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker push $CI_REGISTRY_IMAGE/${CI_JOB_NAME#*:}:latest + +release:encoder: + <<: *drelease + +release:master: + <<: *drelease + +release:master-kube: + <<: *drelease + +release:web: + <<: *drelease + +release:detect: + <<: *drelease + +#validate:web: +# # This actually requires +# # https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1569 to +# # be implemented, which it currently is not yet. So, for now, allow +# # this to fail. +# allow_failure: true +# stage: dockertest +# services: +# - name: postgres:latest +# alias: postgresql +# - name: $CI_REGISTRY_IMAGE/web:$CI_COMMIT_REF_SLUG +# alias: web +# variables: +# POSTGRES_USER: sreview +# POSTGRES_DB: sreview +# SREVIEW_DBISTRING: '"dbi:Pg:dbname=sreview;host=postgres;user=sreview"' +# SREVIEW_ADMINUSER: '"test@example.com"' +# SREVIEW_ADMINPW: '"foo"' +# BASEURL: http://web:8080/ +# image: $CI_REGISTRY_IMAGE/encoder:$CI_COMMIT_REF_SLUG +# script: +# - perl dockerfiles/tests/test.pl --- sreview-0.5.0.orig/.travis.yml +++ sreview-0.5.0/.travis.yml @@ -0,0 +1,20 @@ +--- +language: perl +perl: + - "5.24" + - "5.28" +services: + - postgresql +addons: + postgresql: "9.6" + apt: + packages: + - ffmpeg + - inkscape + - bs1770gain +env: + - SREVIEW_TESTDB=sreview SREVIEW_NONSTRICT=1 +before_script: + - psql -c "create database $SREVIEW_TESTDB;" -U postgres +script: + - perl Makefile.PL && make TEST_VERBOSE=1 test --- sreview-0.5.0.orig/MANIFEST +++ sreview-0.5.0/MANIFEST @@ -70,5 +70,3 @@ web/templates/talk_update.html.ep web/templates/volunteer/list.html.ep web/templates/schedule/index.html.ep -META.yml Module YAML meta-data (added by MakeMaker) -META.json Module JSON meta-data (added by MakeMaker) --- sreview-0.5.0.orig/debian/changelog +++ sreview-0.5.0/debian/changelog @@ -0,0 +1,122 @@ +sreview (0.5.0-1) unstable; urgency=medium + + * New upstream release (too many changes to enumerate). + + -- Wouter Verhelst Sat, 25 Jan 2020 23:21:50 +0200 + +sreview (0.4.0-2) unstable; urgency=medium + + * Whoops, don't forget to set the version number elsewhere, too + + -- Wouter Verhelst Sun, 17 Feb 2019 10:42:32 +0200 + +sreview (0.4.0-1) unstable; urgency=medium + + * The "FOSDEM 2019" release + - Completely overhauled review UI. Thanks, Belen Barros Pena for + your help with this! + + -- Wouter Verhelst Sun, 17 Feb 2019 10:33:58 +0200 + +sreview (0.3.1-2) unstable; urgency=medium + + * Actually drop dh_sysuser usage, also from debian/rules -- whoops. + Closes: #901542 + + -- Wouter Verhelst Tue, 19 Jun 2018 08:36:43 +0200 + +sreview (0.3.1-1) unstable; urgency=medium + + * New upstream release (minor updates) + * Drop dh_sysuser usage, it hasn't been tested with NSS modules, just + add an adduser call instead. Closes: 901136. + + -- Wouter Verhelst Thu, 14 Jun 2018 08:38:56 +0200 + +sreview (0.3.0-1) unstable; urgency=medium + + * The "FOSDEM worked" release. + * New upstream release, featuring: + - Much less bugs (which were discovered after a production run at + FOSDEM) + - More flexibility in certain areas, needed by the above production + run + - Better documentation (thanks Nattie!) + - Working support for JSON export to Stefano Rivera's youtube upload + tool + - A new state for "ignored" talks (i.e., content that is not + interesting) + - Default to self-installed jQuery and bootstrap libraries, rather + than using CDN-provided ones + - ... and much more! + + -- Wouter Verhelst Tue, 13 Feb 2018 12:39:41 +0100 + +sreview (0.2.6-1) unstable; urgency=medium + + * New upstream release (sortof) + + -- Wouter Verhelst Wed, 31 Jan 2018 17:45:03 +0100 + +sreview (0.2.4-1) unstable; urgency=medium + + * New upstream release + + -- Wouter Verhelst Fri, 26 Jan 2018 13:39:19 +0100 + +sreview (0.2.3-3) unstable; urgency=medium + + * Re-upload to unstable, now that the first major issues are fixed. + + -- Wouter Verhelst Thu, 04 Jan 2018 18:18:13 +0100 + +sreview (0.2.3-2) experimental; urgency=medium + + * debian/rules: ensure sreview-master.install and sreview-master.dbc + are executable. + + -- Wouter Verhelst Mon, 01 Jan 2018 20:44:22 +0100 + +sreview (0.2.3-1) experimental; urgency=medium + + * New upstream release (minor changes) + * debian/control: add missing build-depends. Closes: #884546. + + -- Wouter Verhelst Mon, 01 Jan 2018 03:16:05 +0100 + +sreview (0.2.2-1) experimental; urgency=medium + + * New upstream release (with only a few minor changes) + * debian/copyright: add text of the CC "by" license, too. + + -- Wouter Verhelst Mon, 11 Dec 2017 22:25:31 +0100 + +sreview (0.2.1-1) experimental; urgency=medium + + * New upstream release + + -- Wouter Verhelst Wed, 29 Nov 2017 17:15:47 +0100 + +sreview (0.2.0-1) experimental; urgency=medium + + * New upstream release + - Uses a test video that is CC BY 3.0 instead of the one that was CC + BY 2.0, which is not DFSG-free + - Autodetects whether to use libfdk_aac based on whether ffmpeg + supports it, rather than depending on the user to manually set it. + * Add various fixes to the debian/ directory to make lintian slightly happier + + -- Wouter Verhelst Wed, 22 Nov 2017 21:46:04 +0100 + +sreview (0.1.1-1) experimental; urgency=medium + + * New upstream release + Fixes a few oops kind of thingies. + + -- Wouter Verhelst Mon, 13 Nov 2017 11:50:08 +0100 + +sreview (0.1-1) experimental; urgency=medium + + * Initial release. + + -- Wouter Verhelst Fri, 10 Nov 2017 10:14:20 +0100 --- sreview-0.5.0.orig/debian/compat +++ sreview-0.5.0/debian/compat @@ -0,0 +1 @@ +10 --- sreview-0.5.0.orig/debian/control +++ sreview-0.5.0/debian/control @@ -0,0 +1,70 @@ +Source: sreview +Section: video +Priority: optional +Standards-Version: 4.1.1 +Maintainer: Wouter Verhelst +Vcs-Browser: https://salsa.debian.org/wouter/sreview +Vcs-Git: https://salsa.debian.org/wouter/sreview +Build-Depends: debhelper (>= 10), ffmpeg, dh-exec, dh-apache2, libmojo-pg-perl, libmoose-perl, libdatetime-perl, libextutils-depends-perl + +Package: sreview-master +Architecture: all +Depends: sreview-common, ${misc:Depends} +Recommends: libxml-rss-perl, postgresql, postgresql-contrib, libemail-simple-perl, libemail-sender-perl, gridengine-master, sreview-detect, libnet-ssh-authorizedkeysfile-perl, rsync +Description: SReview components for master host + SReview is a video review and transcoding system. It allows users to + review videos, and will then (after the review has finished) transcode + them into archive-quality video files. + . + This package contains the components that should run on just one + server. It is not useful without one or more machines also running the + webinterface (in the sreview-web package) or the encoder (in the + sreview-encoder package). + +Package: sreview-web +Architecture: all +Depends: sreview-common, libjs-bootstrap, pwgen, ${misc:Depends} +Recommends: httpd +Description: SReview webinterface + SReview is a video review and transcoding system. It allows users to + review videos, and will then (after the review has finished) transcode + them into archive-quality video files. + . + This package contains the sreview webinterface, used by administrators + and reviewers. + +Package: sreview-encoder +Architecture: all +Depends: sreview-common, ${misc:Depends}, bs1770gain, inkscape, ${perl:Depends}, libemail-simple-perl, libemail-sender-perl +Recommends: gridengine-exec +Description: SReview encoder code + SReview is a video review and transcoding system. It allows users to + review videos, and will then (after the review has finished) transcode + them into archive-quality video files. + . + This package contains the encoder scripts that do all the hard work. + It should be installed on the machines which will do the actual + transcoding. + +Package: sreview-common +Architecture: all +Depends: libmojolicious-perl, libmojo-pg-perl, libmoose-perl, libdatetime-perl, libdatetime-format-iso8601-perl, libnet-amazon-s3-perl, ffmpeg, ${misc:Depends}, pwgen +Description: SReview -- common code + SReview is a video review and transcoding system. It allows users to + review videos, and will then (after the review has finished) transcode + them into archive-quality video files. + . + This package contains the common code used by all the other SReview + packages. + +Package: sreview-detect +Architecture: all +Depends: sreview-common, ${misc:Depends} +Description: SReview input detection script + SReview is a video review and transcoding system. It allows users to + review videos, and will then (after the review has finished) transcode + them into archive-quality video files. + . + This package contains the sreview-detect script, which probes files in + the input directory and either adds them to the database if they're + new, or just updates their length if they're already known. --- sreview-0.5.0.orig/debian/copyright +++ sreview-0.5.0/debian/copyright @@ -0,0 +1,978 @@ +SReview was written and packaged by Wouter Verhelst + +It is released under the AGPLv3 or (at your option) any later version. + +Additionally, the source comes with a short test video snippet in +t/testvids which was taken from the "Big Buck Bunny" movie, which is +Copyright(c) 2008, the Blender Foundation / www.bigbuckbunny.org, and is +released under the Creative Commons "by" license, version 3.0. + +The full text of the AGPLv3 and the CC "by" license follow. + +----------------------------------------------------------------------------- + + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. + +----------------------------------------------------------------------------- + +Creative Commons +Attribution 3.0 Unported + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE +LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN +ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION +ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE +INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM +ITS USE. + +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE +COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY +COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS +AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE +TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY +BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS +CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND +CONDITIONS. + +1. Definitions + + "Adaptation" means a work based upon the Work, or upon the Work and +other pre-existing works, such as a translation, adaptation, derivative +work, arrangement of music or other alterations of a literary or +artistic work, or phonogram or performance and includes cinematographic +adaptations or any other form in which the Work may be recast, +transformed, or adapted including in any form recognizably derived from +the original, except that a work that constitutes a Collection will not +be considered an Adaptation for the purpose of this License. For the +avoidance of doubt, where the Work is a musical work, performance or +phonogram, the synchronization of the Work in timed-relation with a +moving image ("synching") will be considered an Adaptation for the +purpose of this License. + "Collection" means a collection of literary or artistic works, such +as encyclopedias and anthologies, or performances, phonograms or +broadcasts, or other works or subject matter other than works listed in +Section 1(f) below, which, by reason of the selection and arrangement of +their contents, constitute intellectual creations, in which the Work is +included in its entirety in unmodified form along with one or more other +contributions, each constituting separate and independent works in +themselves, which together are assembled into a collective whole. A work +that constitutes a Collection will not be considered an Adaptation (as +defined above) for the purposes of this License. + "Distribute" means to make available to the public the original and +copies of the Work or Adaptation, as appropriate, through sale or other +transfer of ownership. + "Licensor" means the individual, individuals, entity or entities +that offer(s) the Work under the terms of this License. + "Original Author" means, in the case of a literary or artistic work, +the individual, individuals, entity or entities who created the Work or +if no individual or entity can be identified, the publisher; and in +addition (i) in the case of a performance the actors, singers, +musicians, dancers, and other persons who act, sing, deliver, declaim, +play in, interpret or otherwise perform literary or artistic works or +expressions of folklore; (ii) in the case of a phonogram the producer +being the person or legal entity who first fixes the sounds of a +performance or other sounds; and, (iii) in the case of broadcasts, the +organization that transmits the broadcast. "Work" means the literary +and/or artistic work offered under the terms of this License including +without limitation any production in the literary, scientific and +artistic domain, whatever may be the mode or form of its expression +including digital form, such as a book, pamphlet and other writing; a +lecture, address, sermon or other work of the same nature; a dramatic or +dramatico-musical work; a choreographic work or entertainment in dumb +show; a musical composition with or without words; a cinematographic +work to which are assimilated works expressed by a process analogous to +cinematography; a work of drawing, painting, architecture, sculpture, +engraving or lithography; a photographic work to which are assimilated +works expressed by a process analogous to photography; a work of applied +art; an illustration, map, plan, sketch or three-dimensional work +relative to geography, topography, architecture or science; a +performance; a broadcast; a phonogram; a compilation of data to the +extent it is protected as a copyrightable work; or a work performed by a +variety or circus performer to the extent it is not otherwise considered +a literary or artistic work. "You" means an individual or entity +exercising rights under this License who has not previously violated the +terms of this License with respect to the Work, or who has received +express permission from the Licensor to exercise rights under this +License despite a previous violation. "Publicly Perform" means to +perform public recitations of the Work and to communicate to the public +those public recitations, by any means or process, including by wire or +wireless means or public digital performances; to make available to the +public Works in such a way that members of the public may access these +Works from a place and at a place individually chosen by them; to +perform the Work to the public by any means or process and the +communication to the public of the performances of the Work, including +by public digital performance; to broadcast and rebroadcast the Work by +any means including signs, sounds or images. "Reproduce" means to make +copies of the Work by any means including without limitation by sound or +visual recordings and the right of fixation and reproducing fixations of +the Work, including storage of a protected performance or phonogram in +digital form or other electronic medium. + +2. Fair Dealing Rights. Nothing in this License is intended to reduce, +limit, or restrict any uses free from copyright or rights arising from +limitations or exceptions that are provided for in connection with the +copyright protection under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, +Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +perpetual (for the duration of the applicable copyright) license to +exercise the rights in the Work as stated below: + + to Reproduce the Work, to incorporate the Work into one or more +Collections, and to Reproduce the Work as incorporated in the +Collections; to create and Reproduce Adaptations provided that any such +Adaptation, including any translation in any medium, takes reasonable +steps to clearly label, demarcate or otherwise identify that changes +were made to the original Work. For example, a translation could be +marked "The original work was translated from English to Spanish," or a +modification could indicate "The original work has been modified."; to +Distribute and Publicly Perform the Work including as incorporated in +Collections; and, to Distribute and Publicly Perform Adaptations. + + For the avoidance of doubt: Non-waivable Compulsory License Schemes. +In those jurisdictions in which the right to collect royalties through +any statutory or compulsory licensing scheme cannot be waived, the +Licensor reserves the exclusive right to collect such royalties for any +exercise by You of the rights granted under this License; Waivable +Compulsory License Schemes. In those jurisdictions in which the right to +collect royalties through any statutory or compulsory licensing scheme +can be waived, the Licensor waives the exclusive right to collect such +royalties for any exercise by You of the rights granted under this +License; and, Voluntary License Schemes. The Licensor waives the right +to collect royalties, whether individually or, in the event that the +Licensor is a member of a collecting society that administers voluntary +licensing schemes, via that society, from any exercise by You of the +rights granted under this License. + +The above rights may be exercised in all media and formats whether now +known or hereafter devised. The above rights include the right to make +such modifications as are technically necessary to exercise the rights +in other media and formats. Subject to Section 8(f), all rights not +expressly granted by Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly +made subject to and limited by the following restrictions: + + You may Distribute or Publicly Perform the Work only under the terms +of this License. You must include a copy of, or the Uniform Resource +Identifier (URI) for, this License with every copy of the Work You +Distribute or Publicly Perform. You may not offer or impose any terms on +the Work that restrict the terms of this License or the ability of the +recipient of the Work to exercise the rights granted to that recipient +under the terms of the License. You may not sublicense the Work. You +must keep intact all notices that refer to this License and to the +disclaimer of warranties with every copy of the Work You Distribute or +Publicly Perform. When You Distribute or Publicly Perform the Work, You +may not impose any effective technological measures on the Work that +restrict the ability of a recipient of the Work from You to exercise the +rights granted to that recipient under the terms of the License. This +Section 4(a) applies to the Work as incorporated in a Collection, but +this does not require the Collection apart from the Work itself to be +made subject to the terms of this License. If You create a Collection, +upon notice from any Licensor You must, to the extent practicable, +remove from the Collection any credit as required by Section 4(b), as +requested. If You create an Adaptation, upon notice from any Licensor +You must, to the extent practicable, remove from the Adaptation any +credit as required by Section 4(b), as requested. If You Distribute, or +Publicly Perform the Work or any Adaptations or Collections, You must, +unless a request has been made pursuant to Section 4(a), keep intact all +copyright notices for the Work and provide, reasonable to the medium or +means You are utilizing: (i) the name of the Original Author (or +pseudonym, if applicable) if supplied, and/or if the Original Author +and/or Licensor designate another party or parties (e.g., a sponsor +institute, publishing entity, journal) for attribution ("Attribution +Parties") in Licensor's copyright notice, terms of service or by other +reasonable means, the name of such party or parties; (ii) the title of +the Work if supplied; (iii) to the extent reasonably practicable, the +URI, if any, that Licensor specifies to be associated with the Work, +unless such URI does not refer to the copyright notice or licensing +information for the Work; and (iv) , consistent with Section 3(b), in +the case of an Adaptation, a credit identifying the use of the Work in +the Adaptation (e.g., "French translation of the Work by Original +Author," or "Screenplay based on original Work by Original Author"). The +credit required by this Section 4 (b) may be implemented in any +reasonable manner; provided, however, that in the case of a Adaptation +or Collection, at a minimum such credit will appear, if a credit for all +contributing authors of the Adaptation or Collection appears, then as +part of these credits and in a manner at least as prominent as the +credits for the other contributing authors. For the avoidance of doubt, +You may only use the credit required by this Section for the purpose of +attribution in the manner set out above and, by exercising Your rights +under this License, You may not implicitly or explicitly assert or imply +any connection with, sponsorship or endorsement by the Original Author, +Licensor and/or Attribution Parties, as appropriate, of You or Your use +of the Work, without the separate, express prior written permission of +the Original Author, Licensor and/or Attribution Parties. Except as +otherwise agreed in writing by the Licensor or as may be otherwise +permitted by applicable law, if You Reproduce, Distribute or Publicly +Perform the Work either by itself or as part of any Adaptations or +Collections, You must not distort, mutilate, modify or take other +derogatory action in relation to the Work which would be prejudicial to +the Original Author's honor or reputation. Licensor agrees that in those +jurisdictions (e.g. Japan), in which any exercise of the right granted +in Section 3(b) of this License (the right to make Adaptations) would be +deemed to be a distortion, mutilation, modification or other derogatory +action prejudicial to the Original Author's honor and reputation, the +Licensor will waive or not assert, as appropriate, this Section, to the +fullest extent permitted by the applicable national law, to enable You +to reasonably exercise Your right under Section 3(b) of this License +(right to make Adaptations) but not otherwise. + +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR +OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY +KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, +INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, +FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF +LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, +WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE +EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE +LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR +ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES +ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + + This License and the rights granted hereunder will terminate +automatically upon any breach by You of the terms of this License. +Individuals or entities who have received Adaptations or Collections +from You under this License, however, will not have their licenses +terminated provided such individuals or entities remain in full +compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will +survive any termination of this License. Subject to the above terms and +conditions, the license granted here is perpetual (for the duration of +the applicable copyright in the Work). Notwithstanding the above, +Licensor reserves the right to release the Work under different license +terms or to stop distributing the Work at any time; provided, however +that any such election will not serve to withdraw this License (or any +other license that has been, or is required to be, granted under the +terms of this License), and this License will continue in full force and +effect unless terminated as stated above. + +8. Miscellaneous + + Each time You Distribute or Publicly Perform the Work or a +Collection, the Licensor offers to the recipient a license to the Work +on the same terms and conditions as the license granted to You under +this License. Each time You Distribute or Publicly Perform an +Adaptation, Licensor offers to the recipient a license to the original +Work on the same terms and conditions as the license granted to You +under this License. If any provision of this License is invalid or +unenforceable under applicable law, it shall not affect the validity or +enforceability of the remainder of the terms of this License, and +without further action by the parties to this agreement, such provision +shall be reformed to the minimum extent necessary to make such provision +valid and enforceable. No term or provision of this License shall be +deemed waived and no breach consented to unless such waiver or consent +shall be in writing and signed by the party to be charged with such +waiver or consent. This License constitutes the entire agreement +between the parties with respect to the Work licensed here. There are no +understandings, agreements or representations with respect to the Work +not specified here. Licensor shall not be bound by any additional +provisions that may appear in any communication from You. This License +may not be modified without the mutual written agreement of the Licensor +and You. The rights granted under, and the subject matter referenced, +in this License were drafted utilizing the terminology of the Berne +Convention for the Protection of Literary and Artistic Works (as amended +on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright +Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and +the Universal Copyright Convention (as revised on July 24, 1971). These +rights and subject matter take effect in the relevant jurisdiction in +which the License terms are sought to be enforced according to the +corresponding provisions of the implementation of those treaty +provisions in the applicable national law. If the standard suite of +rights granted under applicable copyright law includes additional rights +not granted under this License, such additional rights are deemed to be +included in the License; this License is not intended to restrict the +license of any rights under applicable law. + + Creative Commons Notice + + Creative Commons is not a party to this License, and makes no +warranty whatsoever in connection with the Work. Creative Commons will +not be liable to You or any party on any legal theory for any damages +whatsoever, including without limitation any general, special, +incidental or consequential damages arising in connection to this +license. Notwithstanding the foregoing two (2) sentences, if Creative +Commons has expressly identified itself as the Licensor hereunder, it +shall have all rights and obligations of Licensor. + + Except for the limited purpose of indicating to the public that the +Work is licensed under the CCPL, Creative Commons does not authorize the +use by either party of the trademark "Creative Commons" or any related +trademark or logo of Creative Commons without the prior written consent +of Creative Commons. Any permitted use will be in compliance with +Creative Commons' then-current trademark usage guidelines, as may be +published on its website or otherwise made available upon request from +time to time. For the avoidance of doubt, this trademark restriction +does not form part of this License. + + Creative Commons may be contacted at https://creativecommons.org/. --- sreview-0.5.0.orig/debian/rules +++ sreview-0.5.0/debian/rules @@ -0,0 +1,12 @@ +#!/usr/bin/make -f + +%: + dh $@ --with apache2 + +override_dh_install: + chmod +x debian/sreview-master.install debian/sreview-master.dbc + dh_install + +override_dh_installinit: + dh_installinit -psreview-master --name=sreview-dispatch + dh_installinit --remaining --- sreview-0.5.0.orig/debian/sreview-common.dirs +++ sreview-0.5.0/debian/sreview-common.dirs @@ -0,0 +1 @@ +etc/sreview --- sreview-0.5.0.orig/debian/sreview-common.install +++ sreview-0.5.0/debian/sreview-common.install @@ -0,0 +1,17 @@ +usr/share/man/man1/sreview-config* +usr/share/man/man1/sreview-user* +usr/share/man/man3/SReview::Video* +usr/share/perl5/SReview.pm +usr/share/perl5/SReview/CodecMap.pm +usr/share/perl5/SReview/Config/ +usr/share/perl5/SReview/Config.pm +usr/share/perl5/SReview/Db.pm +usr/share/perl5/SReview/Files/ +usr/share/perl5/SReview/Map.pm +usr/share/perl5/SReview/Talk.pm +usr/share/perl5/SReview/Talk/State.pm +usr/share/perl5/SReview/Video/ +usr/share/perl5/SReview/Video.pm +usr/share/perl5/SReview/Videopipe.pm +usr/bin/sreview-config +usr/bin/sreview-user --- sreview-0.5.0.orig/debian/sreview-common.postinst +++ sreview-0.5.0/debian/sreview-common.postinst @@ -0,0 +1,7 @@ +#!/bin/sh + +#DEBHELPER# + +if ! id sreview > /dev/null 2>&1; then + adduser --system --group --home /var/lib/sreview sreview +fi --- sreview-0.5.0.orig/debian/sreview-common.postrm +++ sreview-0.5.0/debian/sreview-common.postrm @@ -0,0 +1,10 @@ +#!/bin/sh + +set -e + +if [ $1 = "purge" ] +then + rm -f /etc/sreview/config.pm +fi + +#DEBHELPER# --- sreview-0.5.0.orig/debian/sreview-detect.cron.d +++ sreview-0.5.0/debian/sreview-detect.cron.d @@ -0,0 +1 @@ +*/30 * * * * sreview [ -x /usr/bin/sreview-detect ] && /usr/bin/sreview-detect --- sreview-0.5.0.orig/debian/sreview-detect.install +++ sreview-0.5.0/debian/sreview-detect.install @@ -0,0 +1,2 @@ +usr/bin/sreview-detect +usr/share/man/man1/sreview-detect* --- sreview-0.5.0.orig/debian/sreview-encoder.install +++ sreview-0.5.0/debian/sreview-encoder.install @@ -0,0 +1,12 @@ +usr/bin/sreview-cut +usr/bin/sreview-transcode +usr/bin/sreview-previews +usr/bin/sreview-skip +usr/bin/sreview-notify +usr/bin/sreview-upload +usr/bin/sreview-encode +usr/share/man/man1/sreview-skip* +usr/share/man/man1/sreview-cut* +usr/share/man/man1/sreview-previews* +usr/share/man/man1/sreview-transcode* +usr/share/man/man1/sreview-encode* --- sreview-0.5.0.orig/debian/sreview-master.config +++ sreview-0.5.0/debian/sreview-master.config @@ -0,0 +1,15 @@ +#!/bin/sh + +set -e + +dbc_dbuser=sreview +dbc_dbname=sreview +dbc_dbtypes=pgsql + +. /usr/share/debconf/confmodule +if [ -f /usr/share/dbconfig-common/dpkg/config.pgsql ]; then + . /usr/share/dbconfig-common/dpkg/config.pgsql + dbc_go sreview-master "$@" +fi + +#DEBHELPER# --- sreview-0.5.0.orig/debian/sreview-master.dbc +++ sreview-0.5.0/debian/sreview-master.dbc @@ -0,0 +1,49 @@ +#!/bin/sh + +set -e + +. /etc/dbconfig-common/sreview-master.conf + +if [ $dbc_dbtype = "none" ]; +then + exit # User wants do things manually +fi + +[ $dbc_dbtype = "pgsql" ] # we can't work with anything but postgres + +dbistring="dbi:Pg:dbname='$dbc_dbname'" + +if [ ! -z "$dbc_dbuser" ] +then + dbistring="$dbistring;user='$dbc_dbuser'" +fi + +if [ ! -z "$dbc_dbpass" ] +then + dbistring="$dbistring;password='$dbc_dbpass'" +fi + +if [ ! -z "$dbc_dbserver" ] +then + dbistring="$dbistring;host='$dbc_dbserver'" +fi + +if [ ! -z "$dbc_dbport" ] +then + dbistring="$dbistring;host='$dbc_dbport'" +fi + +if [ "$dbc_ssl" = "true" ] +then + dbistring="$dbistring;sslmode=require" +fi + +if [ "$dbc_authmethod_admin" = "ident" ] +then + su - $dbc_dbadmin -c "psql $dbc_dbname -c 'create extension if not exists pgcrypto; create extension if not exists plpgsql'" +else + psql -U $dbc_dbadmin $dbc_dbname -c 'create extension if not exists pgcrypto; create extension if not exists plpgsql' +fi + +sreview-config --set=dbistring="$dbistring" --action=update +sreview-config --action=initdb --- sreview-0.5.0.orig/debian/sreview-master.install +++ sreview-0.5.0/debian/sreview-master.install @@ -0,0 +1,4 @@ +#!/usr/bin/dh-exec +usr/bin/sreview-dispatch +usr/share/man/man1/sreview-dispatch* +debian/sreview-master.dbc => usr/share/dbconfig-common/scripts/sreview-master/install/pgsql --- sreview-0.5.0.orig/debian/sreview-master.postinst +++ sreview-0.5.0/debian/sreview-master.postinst @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e + +. /usr/share/debconf/confmodule + +if [ -f /usr/share/dbconfig-common/dpkg/postinst.pgsql ]; then + . /usr/share/dbconfig-common/dpkg/postinst.pgsql + dbc_go sreview-master "$@" +fi + +#DEBHELPER# --- sreview-0.5.0.orig/debian/sreview-master.postrm +++ sreview-0.5.0/debian/sreview-master.postrm @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e + +. /usr/share/debconf/confmodule + +if [ -f /usr/share/dbconfig-common/dpkg/postrm.pgsql ]; then + . /usr/share/dbconfig-common/dpkg/postrm.pgsql + dbc_go sreview-master "$@" +fi + +#DEBHELPER# --- sreview-0.5.0.orig/debian/sreview-master.prerm +++ sreview-0.5.0/debian/sreview-master.prerm @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e + +. /usr/share/debconf/confmodule + +if [ -f /usr/share/dbconfig-common/dpkg/prerm.pgsql ]; then + . /usr/share/dbconfig-common/dpkg/prerm.pgsql + dbc_go sreview-master "$@" +fi + +#DEBHELPER# --- sreview-0.5.0.orig/debian/sreview-master.sreview-dispatch.service +++ sreview-0.5.0/debian/sreview-master.sreview-dispatch.service @@ -0,0 +1,10 @@ +[Unit] +Description=SReview dispatch service +Requires=postgresql.service +Requires=gridengine-master.service +[Service] +User=sreview +Group=sreview +ExecStart=/usr/bin/sreview-dispatch +[Install] +WantedBy=multi-user.target --- sreview-0.5.0.orig/debian/sreview-web.apache2 +++ sreview-0.5.0/debian/sreview-web.apache2 @@ -0,0 +1 @@ +site debian/sreview-web.conf --- sreview-0.5.0.orig/debian/sreview-web.conf +++ sreview-0.5.0/debian/sreview-web.conf @@ -0,0 +1,46 @@ + + ServerName sreview.example.com + DocumentRoot /usr/share/sreview/public + + + Require all granted + AllowOverride None + Options +Indexes + Redirect permanent / https://sreview.example.com + + + + ServerName sreview.example.com + + + UserDir disabled + + DocumentRoot /usr/share/sreview/public + + Require all granted + Options +Indexes + + Alias /bootstrap /usr/share/javascript/bootstrap + Alias /jquery /usr/share/javascript/jquery + + Require all granted + + + Require all granted + + SSLEngine on + SSLProtocol -ALL +TLSv1.2 + # SSLCertificateFile + # SSLCertificateKeyFile + # SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH EDH+aRSA !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS" + Header set Strict-Transport-Security max-age=15768000 + Header set X-Frame-Options DENY + Header set X-Content-Type-Options nosniff + ProxyRequests off + ProxyPreserveHost On + ProxyPass /video ! + ProxyPass /.well-known ! + ProxyPass / http://localhost:8080/ keepalive=on + ProxyPassReverse / http://localhost:8080/ + RequestHeader set X-Forwarded-Proto "https" + --- sreview-0.5.0.orig/debian/sreview-web.install +++ sreview-0.5.0/debian/sreview-web.install @@ -0,0 +1,7 @@ +usr/share/perl5/SReview/Web.pm +usr/share/perl5/SReview/Web/ +usr/share/perl5/SReview/Access.pm +usr/share/perl5/SReview/API +usr/bin/sreview-web +web/templates usr/share/sreview +web/public usr/share/sreview --- sreview-0.5.0.orig/debian/sreview-web.postinst +++ sreview-0.5.0/debian/sreview-web.postinst @@ -0,0 +1,5 @@ +#!/bin/sh + +sreview-config --set=secret=$(pwgen -s 40 -n 1) --action=update + +#DEBHELPER# --- sreview-0.5.0.orig/debian/sreview-web.service +++ sreview-0.5.0/debian/sreview-web.service @@ -0,0 +1,11 @@ +[Unit] +Description=SReview web service +[Service] +ExecStart=/usr/bin/hypnotoad -f /usr/bin/sreview-web +ExecStop=/usr/bin/hypnotoad -s /usr/bin/sreview-web +RuntimeDirectory=sreview +User=sreview +Group=sreview +PIDFile=/var/run/sreview/sreview-web.pid +[Install] +WantedBy=multi-user.target --- sreview-0.5.0.orig/dockerfiles/common/Dockerfile +++ sreview-0.5.0/dockerfiles/common/Dockerfile @@ -0,0 +1,3 @@ +FROM debian:stable +COPY *.deb /root/ +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y --no-install-recommends install /root/sreview-common*deb && apt-get clean --- sreview-0.5.0.orig/dockerfiles/detect/Dockerfile +++ sreview-0.5.0/dockerfiles/detect/Dockerfile @@ -0,0 +1,5 @@ +ARG ci_registry_image +ARG ci_commit_ref_slug +FROM $ci_registry_image/common:$ci_commit_ref_slug +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends /root/sreview-detect*deb +ENTRYPOINT /usr/bin/sreview-detect --- sreview-0.5.0.orig/dockerfiles/encoder/Dockerfile +++ sreview-0.5.0/dockerfiles/encoder/Dockerfile @@ -0,0 +1,4 @@ +ARG ci_registry_image +ARG ci_commit_ref_slug +FROM $ci_registry_image/common:$ci_commit_ref_slug +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y --no-install-recommends install /root/sreview-encoder*deb libdevel-trace-perl curl --- sreview-0.5.0.orig/dockerfiles/master-kube/Dockerfile +++ sreview-0.5.0/dockerfiles/master-kube/Dockerfile @@ -0,0 +1,6 @@ +ARG ci_registry_image +ARG ci_commit_ref_slug +FROM $ci_registry_image/master:$ci_commit_ref_slug +COPY run-kube /usr/local/bin/run-kube +COPY config.pm /etc/sreview/config.pm +RUN apt-get update && apt-get -y install curl libjson-perl libjson-xs-perl libyaml-libyaml-perl libdatetime-perl && curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl && apt-get --purge -y remove curl && apt-get --purge -y autoremove && chmod +x kubectl && mv kubectl /usr/local/bin/ --- sreview-0.5.0.orig/dockerfiles/master-kube/config.pm +++ sreview-0.5.0/dockerfiles/master-kube/config.pm @@ -0,0 +1,10 @@ +$state_actions = { + 'announcing' => 'run-kube announce <%== $talkid %>', + 'cutting' => 'run-kube cut <%== $talkid %>', + 'generating_previews' => 'run-kube previews <%== $talkid %>', + 'notification' => 'run-kube notify <%== $talkid %>', + 'transcoding' => 'run-kube transcode <%== $talkid %>', + 'uploading' => 'run-kube upload <%== $talkid %>', +}; + +1; --- sreview-0.5.0.orig/dockerfiles/master-kube/run-kube +++ sreview-0.5.0/dockerfiles/master-kube/run-kube @@ -0,0 +1,98 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use SReview::Talk; +use SReview::Config::Common; +use YAML::XS; +use JSON; +use DateTime; + +my $task = shift; +my $id = shift; + +my $json = JSON->new->allow_nonref; + +my $config = SReview::Config::Common::setup; + +my %task_configs = ( + previews => [ "pubdir", "accessmethods", "s3_access_config",], + cut => [ "workdir", "pubdir", "audio_multiplex_mode", "accessmethods", "s3_access_config","inputglob",], + transcode => [ "pubdir", "outputdir", "output_subdirs", "workdir", "preroll_template", "postroll_template", "postroll", "apology_template", "input_profile", "output_profiles", "extra_profiles", "accessmethods", "s3_access_config", ], + notify => [ "notify_email_template", "notify_email_subject", "notify_commands", "notify_actions", "email_template", "email_subject", "email_from", "urlbase", ], + announce => [ "announce_email_template", "announce_email_subject", "announce_commands", "announce_actions", "email_template", "email_subject", "email_from", "urlbase", ], + upload => [ "upload_actions", "event", "outputdir", "output_subdirs", "cleanup", "output_profiles", "extra_profiles", "accessmethods", "s3_access_config", ], +); + +my $talk = SReview::Talk->new(talkid => $id); +my $now = DateTime->now; +my $start = DateTime->new( + year => $now->year, + month => $now->month, + day => 1, + hour => 0, + minute => 0, + second => 0, +); +my $secs = $now->epoch - $start->epoch; +my $jobsuffix = $talk->talkid . "-" . unpack("h*", pack("L", $secs)); + +my $image = "registry.salsa.debian.org/debconf-video-team/sreview/encoder"; +if(exists($ENV{SREVIEW_ENCODER_IMAGE})) { + $image = $ENV{SREVIEW_ENCODER_IMAGE}; +} + +my $yamldata = { + apiVersion => "batch/v1", + kind => "Job", + metadata => {name => "$task-$jobsuffix"}, + spec => { + template => { + metadata => { + name => "$task-$jobsuffix", + }, + spec => { + restartPolicy => "OnFailure", + containers => [ { + name => "encoder", + image => $image, + imagePullPolicy => "Always", + command => ["sreview-$task"], + args => [ $talk->talkid ], + env => [ { + name => "SREVIEW_DBISTRING", + valueFrom => { + secretKeyRef => { + name => "sreview-secret", + key => "SREVIEW_DBISTRING", + }, + } + } ], + } ], + }, + }, + }, +}; + +if($task eq "announce") { + $yamldata->{spec}{template}{spec}{containers}[0]{command}[0] = "sreview-notify"; + push @{$yamldata->{spec}{template}{spec}{containers}[0]{args}}, "announce"; +} + +foreach my $cfg (@{$task_configs{$task}}) { + next if $config->is_default($cfg); + my $val = $json->encode($config->get($cfg)); + push @{$yamldata->{spec}{template}{spec}{containers}[0]{env}}, {name => "SREVIEW_" . uc($cfg), value => $val}; +} + +if(exists($ENV{DO_DEBUG})) { + unshift(@{$yamldata->{spec}{template}{spec}{containers}[0]{args}}, ("-d:Trace", "/usr/bin/" . @{$yamldata->{spec}{template}{spec}{containers}[0]{command}})); + $yamldata->{spec}{template}{spec}{containers}[0]{command} = [ "perl" ]; +} + +print "About to create this job:\n"; +print Dump($yamldata) . "\n"; +open my $kubectl, "|-", "kubectl", "apply", "-f", "-"; +print $kubectl Dump($yamldata) . "\n"; +close $kubectl; --- sreview-0.5.0.orig/dockerfiles/master/Dockerfile +++ sreview-0.5.0/dockerfiles/master/Dockerfile @@ -0,0 +1,5 @@ +ARG ci_registry_image +ARG ci_commit_ref_slug +FROM $ci_registry_image/common:$ci_commit_ref_slug +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends /root/sreview-master*deb +ENTRYPOINT /usr/bin/sreview-dispatch --- sreview-0.5.0.orig/dockerfiles/tests/test.pl +++ sreview-0.5.0/dockerfiles/tests/test.pl @@ -0,0 +1,25 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Mojo::UserAgent; +use Mojo::JSON qw/decode_json/; +use SReview::Talk; +use Data::Dumper; + +my $ua = Mojo::UserAgent->new; + +my $res = $ua->post(join('/', $ENV{BASEURL}, 'login_post' => form => { email => $ENV{SREVIEW_ADMINUSER}, pass => $ENV{SREVIEW_ADMINPW} }))->result; + +$res->is_success or die "error " . $res->code . ": " . $res->message; + +my $talk = SReview::Talk->new(talkid => 1); + +$res = $ua->get(join('/', $ENV{BASEURL}, $talk->nonce, 'data'))->result; + +$res->is_success or die "error " . $res->code . ": " . $res->message; + +my $json = decode_json($res->body); + +print Dumper($json); --- sreview-0.5.0.orig/dockerfiles/web/Dockerfile +++ sreview-0.5.0/dockerfiles/web/Dockerfile @@ -0,0 +1,7 @@ +ARG ci_registry_image +ARG ci_commit_ref_slug +FROM $ci_registry_image/common:$ci_commit_ref_slug +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y --no-install-recommends install /root/sreview-web*deb wait-for-it && mkdir -p /var/run/sreview && chown sreview:sreview /var/run/sreview +ENV GIT_DESCRIBE @git_describe@ +ENTRYPOINT ["/usr/bin/hypnotoad","-f","/usr/bin/sreview-web"] +EXPOSE 8080 --- sreview-0.5.0.orig/docs/installation.md +++ sreview-0.5.0/docs/installation.md @@ -0,0 +1,216 @@ +Installing SReview +================== + +This will install SReview in a way that is useful for a small +conference. That is, you expect to have no more than a handful of talks. + +* Install the packages + [sreview-detect](https://packages.debian.org/unstable/sreview-detect), + [sreview-master](https://packages.debian.org/unstable/sreview-master), + [sreview-encoder](https://packages.debian.org/unstable/sreview-encoder), + and + [sreview-web](https://packages.debian.org/unstable/sreview-web) on + a *single* machine. +* The installation will create an sreview user and database, and will + start the `sreview-web` service on port 8080, listening only to + localhost. The sreview-web package also ships with an apache + configuration snippet that shows how to proxy it from the interwebs. + This is enabled by default, but may require an SSL configuration. +* Run `sreview-config --action=dump`. This will show you the current + configuration of SReview. If you want to change something, either edit + `/etc/sreview/config.pm`, or use `sreview-config --set=variable=value + --action=update`. +* Run `sreview-user -d --action=create -u `. This will + create an administrator user in the SReview database. +* Browse to the SReview webinterface (either on http://localhost:8080/, + or on the apache-redirected standard web port). +* Add your schedule to the SReview database. Currently, no generic code + exists [yet](https://github.com/yoe/sreview/issues/53) to do that. + Patches welcome! ;-) +* Decide whether you want notifications: + - If you set the `anonreviews` configuration option to a nonzero + value, then the `/overview` URL in the webinterface will have + links to individual review pages. Reviewers can then just click on + links and do review. However, an out-of-band mechanism for + coordination between reviewers will be required. Note that review + for talks not in the `previews` state will not be possible. + - If you do *not* set that option, but create review volunteer users + through the webinterface, then these users can go to the + `/volunteer/list` URL, where they will receive a number of talks. These + talks will be locked to them; the reviewers will have to finish the + review. + - If you add email addresses to speakers and/or track managers in + the SReview database, and set the `notify_actions` configuration + value to an array including `email`, the `email_template` variable + to a Mojo::Template template for the email, the `email_from` + configuration value to the email sender address, and the `urlbase` + configuration value to the base URL where SReview can be found, + then speakers and/or track managers will receive an email + notification when a talk reaches the `previews` state containing + the review link. + - If you set the `notify_actions` configuration value to an array + including `command`, and `notify_commands` to an array of arrays, + then the command(s) in `notify_commands` will be run when a talk + reaches the `previews` state. This may be used to, e.g., cause an + IRC bot to say something. +* Review the `inputglob` and `parse_re` configuration parameters of + SReview. The first should contain a filename glob pattern that will + find your raw assets; the second should parse any given filename into + room, year, month, day, hour, minute, and second components. +* Provide an SVG file for opening credits, and point to it from the + `preroll_template` configuration value. See the `SVG TRANSFORMATIONS` + section in the sreview-transcode POD documentation for details on how + to do that. +* Provide an SVG or PNG file for closing credits, and point to it from + the `postroll_template` (for SVG) or `postroll` (for PNG) + configuration option. +* Store raw asset files, and make sure `sreview-detect` runs (it should + do so from cron once every half hour by default). + +The above configuration should work, and will be sufficient for a small +conference. The downside, however, is that there will be only one +backend process running at all times. When a talk reaches the `cutting` +state, but no `sreview-dispatch` process is available immediately, it +may take a *long* time for a process to become available for use. + +There are two ways to fix that: + +Using multiple cores +-------------------- + +In the default configuration, it is safe to run `sreview-dispatch` +multiple times. Each instance will request one job, run it, and then +request the next job. + +However, it does not allow prioritizing short-running jobs (like +`sreview-cut`, which should never take more than a few minutes) over +long-running ones (like `sreview-transcode`, which may easily take +several hours). The result may be that reviewers may have to wait +before the system produces another review, which is not ideal. + +Using a distributed resource manager +------------------------------------ + +A DRM like gridenine, SLURM, PBS, or Torque allows one to submit a job +and have it be run elsewhere. In such a configuration, you would +configure SReview to submit jobs to the DRM system, and it would then be +up to the DRM system to decide where to run it; e.g., it could be +configured to run high-I/O jobs (like `sreview-cut`) on the file server +where the files are directly available, whereas high-CPU jobs (like +`sreview-transcode`) would run on nodes with many CPU cores and +reasonable network bandwidth but not the fileserver. + +Due to the increased flexibility in managing jobs that way, the author +of SReview *strongly* recommends the use of a DRM system for most +installations, even if SReview only runs on one system. However, because +setting up a DRM system is a lot of work and can be fairly complicated, +this is not the default mode of operation. + +Since the author is most familiar with `gridengine`, a short tutorial on +how to set up a gridengine-based system follows. Instructions for other +DRM systems are welcome. + +- Make sure all hosts have a fixed IP address of some sort. This may be + a VPN IP or something, but it must exist. +- Make sure all hosts' FQDN resolves to that fixed IP address, not to + something like `127.0.0.1` or (like is common on Debian) `127.0.1.1`. +- Make sure that all hosts can resolve eachother by name. This may be + through DNS or it may be through adding entries to `/etc/hosts`. +- Pick one host as the master host. On this host, install the + `gridengine-master`, `gridengine-exec`, and `gridengine-client` + packages. +- Run `qconf -ap smp`. This will open your editor with the configuration + values for the `smp` PE that you're creating. Set the `slots` value to + a number not lower than the total number of CPU cores on *all* the + hosts you will be adding to the network. It's fine to set it to + something ridiculously high, like `9999`. Save and exit. +- Run `qconf -ahgrp @allhosts`. This will open your editor with other + configuration values. On the `hostlist` line, add the hostname of your + master host. Save and exit. +- Run `qconf -aq lowprio.q`. Make the following changes: + - Set the `slots` value here to the number of CPU cores that *each + host* will have. That is, if you have 8 cores on every machine in + your network, set it to 8. If you have 4 cores, set it to 4. If + you have a mix of machines, choose the value that is valid for the + master host for now. + - Set the `pe_list` value to `smp` (we didn't create the `make` and + `mpi` values, because we don't need them). + - Set the `hostlist` value to `@allhosts`. + Save and exit. +- Run `qconf -aq hiprio.q`. Make the same changes as for the + `lowprio.q`, above. In addition to that, set the `subordinate_list` + value to `slots=X(lowprio.q:0:sr)`, where `X` is the value you entered + for `slots`. Save and exit. + +The above creates a gridengine environment with two queues, one called +`lowprio.q` and one called `hiprio.q`. The system is configured such +that gridengine will *never* allow more jobs to be running than you have +CPU cores. However, if all CPUs are busy and a job is submitted in the +`hiprio.q` queue, gridengine will send `SIGSTOP` to all processes +started by the shortest-running job in the `lowprio.q` queue, and then +allow the `hiprio.q` job to be started. Once the `hiprio.q` job +finishes, the `lowprio.q` job will receive a SIGCONT and be allowed to +continue. + +To add more worker machines to the system, install `gridengine-exec` on +all other machines in the network. Then, perform the following tasks on +the master: + +- Run `qconf -ae ` for each of the other hosts. This creates + "exec hosts" in the gridengine configuration. +- Run `qconf -mhgrp @allhosts`, and add the hostnames of the exechosts + to the `hostlist` line. +- If one or more of the newly added exec hosts have more or less CPU + cores than what you configured, then set the number of available slots + on a per-node basis, using the following format: + + slots default,[hostname=value],[hostname=value] + + that is, set the default value for the "slots" configuration parameter + first; and then for each host that should have a different value, use + the format `[hostname=X]` for that host. e.g., if most of your hosts + have 8 cores, but host `newbox.example.com` has 16 and host + `oldthing.example.com` has 4, you would use: + + slots 8,[newbox.example.com=16],[oldthing.example.com=4] + + If you have more hosts than it turns out you need and you want to shut + one down, note that it *is* possible to set the slots value to a value + lower than the number of jobs already running on a particular host; if + you do that, gridengine will allow jobs to be finished but not start + any new jobs until the number of running jobs goes below the new + `slots` value. +- If you prefer that the master host does not run any jobs (e.g., + because it is your fileserver and you don't want it to be distracted + transcoding things), set its number of slots for a given queue to 0. +- Now run `qsub -q lowprio.q sleep 100`, and then run `qstat -q + lowprio.q` a few times. You should see your job listed as `pending` + first, and then after a few seconds it should be queued on a host. + Once the job has run for 100 seconds, it should be removed from the + system (you can remove it forcibly before its time is up by way of + `qdel `, where jobid is the number of the job that `qstat` will + show you and which `qsub` returned in its output). +- If that worked, make sure that all exechosts have access to the files + they need (e.g., through NFS or Samba), that they have + the `sreview-encoder` package installed, and that their + `/etc/sreview/config.pm` synchronized. +- Now, modify the `state_actions` so that rather than directly running + your `sreview-*` commands, they are submitted into the `gridengine` + system. e.g., the following would work: + + $state_actions = { + 'cutting' => 'qsub -cwd -pe smp 1 -b y -V -q hiprio.q -e <%== $output_dir %> -o <%== $output_dir %> -N cut_<%== $talkid %> sreview-cut <%== $talkid %>', + 'generating_previews' => 'qsub -cwd -pe smp 1 -b y -V -q hiprio.q -e <%== $output_dir %> -o <%== $output_dir %> -N preview_<%== $talkid %> sreview-previews <%== $talkid %>', + 'notification' => 'qsub -cwd -pe smp 1 -b y -V -q hiprio.q -e <%== $output_dir %> -o <%== $output_dir %> -N notify_<%== $talkid %> sreview-notify <%== $talkid %>', + 'transcoding' => 'qsub -cwd -pe smp 1 -b y -V -q lowprio.q -e <%== $output_dir %> -o <%== $output_dir %> -N transcode_<%== $talkid %> sreview-transcode <%== $talkid %>', + 'uploading' => 'qsub -cwd -pe smp 1 -b y -V -q lowprio.q -e <%== $output_dir %> -o <%== $output_dir %> -N upload_<%== $talkid %> sreview-upload <%== $talkid %>', + }; + +Help! +===== + +If you need more help, contact wouter: + +- IRC: wouter on OFTC or Freenode +- email: wouter on the debian.org domain + --- sreview-0.5.0.orig/docs/production.md +++ sreview-0.5.0/docs/production.md @@ -0,0 +1,92 @@ +# Production examples + +SReview has been used in production for the following conferences: + +* FOSDEM: 2017 and 2018 edition. +* DebConf: 2017 and 2018 edition. +* A few mini-debconfs: 2017 Cambridge, 2018 Hamburg + +What follows below is a short description of how SReview was installed +and configured for each of those conferences. Note, however, that it is +possible to run all components of SReview on a single host; for small +conferences, doing so is recommended. + +## FOSDEM + +### 2017 + +At FOSDEM 2017, SReview was installed on the following machines: + +- review.video.fosdem.org: web interface, postgresql, gridengine master, + gridengine exec, dispatch script +- encoder0.video.fosdem.org, encoder1.video.fosdem.org, ...: gridengine + exec, encoder nodes +- backend0.video.fosdem.org, backend1.video.fosdem.org, ...: + `detect_files`, storage for raw recordings (access over HTTP only) +- storage0.video.fosdem.org, storage1.video.fosdem.org, ...: NFS, + storage for `cut_talk`, `previews`, and `transcode` output. + +All machines were "bare metal" machines at a cloud hoster. Data access +to the database and the raw files was LAN-based and therefore quick. + +FOSDEM 2017 was the first ever conference where SReview was used. The +code was *extremely* rough around the edges; in fact, much of the +functionality wasn't there yet, and much of what was there has since +been rewritten. + +### 2018 + +The setup for FOSDEM 2018 was very similar, but there were a few +differences: + +- Everything ran from Debian packages, and we used the generic scripts + (`sreview-transcode` rather than a custom `transcode` script, etc). +- review.video.fosdem.org was renamed to review-master.video.fosdem.org, + and a CNAME was created for the former, for more flexibility (which we + turned out not to need in the end, sigh). +- The NGinX-mp4 module was not used this time around to access the + backend machines, instead we used NFS to access them. + +## DebConf + +### 2017 (Montéal, Canada) + +At DebConf17, SReview was installed on the following machines: + +- vittoria.debian.org: web interface, postgresql, gridengine master, + gridengine exec, dispatch script with configuration for upload and + notification *only* (upload script would pull from noc1st0 first and + then publish). +- noc1st0.debconf17.debconf.org: raw recordings, all intermediate files, + nginx (for serving preview files to reviewers), gridengine master, + `detect_files`, NFS server for raw and intermediate files, gridengine + exec (for `cut_talks` scripts *only*). +- encoder0.debconf17.debconf.org, encoder1.debconf17.debconf.org, ...: + gridengine exec, encoder nodes. + +vittoria is a VM that runs on Debian infrastructure at GRNET (Greece). +It is available to the DebConf video team all year. + +The other machines were local hardware, borrowed for the duration of +DebConf17. They were configured to be granted access to *only* the +postgresql on vittoria. + +During the conference, while the talks were in the `waiting_for_files` +through `transcoding` states, no files were copied to vittoria. Only +when the transcodes had finished were they copied (by the `uploads` +script that ran on vittoria) to vittoria, and then pushed from vittoria +to the DebConf [video archive](https://video.debian.net). + +After the conference, all raw recordings were rsync'd to vittoria. + +### 2018 (Hsinchu, Taiwan) + +For DebConf18, the setup was very similar, but there was a major +difference: rather than having multiple machines on-site, the DebConf18 +setup only had one 24-core VM with 10 TiB of storage on-site, +`storage.dc18.debconf.org`. All cutting and transcoding was done on this +machine; vittoria served the webinterface and the database, etc. + +## Mini debconfs + +Everything was installed and run on vittoria.debian.org --- sreview-0.5.0.orig/javascript-test/test.html +++ sreview-0.5.0/javascript-test/test.html @@ -0,0 +1,39 @@ + + + + + + SReview javascript tests + + + +
+
+ + + + + + --- sreview-0.5.0.orig/kubernetes/README.md +++ sreview-0.5.0/kubernetes/README.md @@ -0,0 +1,71 @@ +These files allow for running SReview on a Kubernetes cluster. + +TODO: + +- Actually test this on a real kubernetes cluster. It (seems to) work on + minikube on my laptop, that's all I can say. +- I used this as a project to learn about Kubernetes. Most likely, I've + made mistakes. Patches welcome! +- I was told about the fact that PersistentVolumeClaims aren't really a + good thing for cross-pod communication. Will have to modify SReview so + it can upload/download files before/after working on them... + +Instructions: +- Create a PostgreSQL database instance. Either use "postgres.yaml" to + create one inside kubernetes, or use an external one if you already + have that. Note that the "postgres.yaml" file assumes a + persistentVolumeClaim called "postgresdata" with "ReadWriteOnce" + permissions has been made available. If you use minikube, the + "storage-minikube.yaml" file may help. + Make sure to back up this database somehow (instructions on doing so + is outside the scope of this documentation; please see [the PostgreSQL + website](https://www.postgresql.org) for more details). +- Edit the "config.yaml" file to do configuration. Run `sreview-config + dump` in the `encoder` image to get a list of all the configuration + values with their defaults. You can set them as environment variables + by uppercasing them and prepending `SREVIEW_`; e.g., to configure the + `$event` value, set the environment variable `SREVIEW_EVENT`. All + values should be encoded as JSON; this means that to encode a string, + you should double-quote them in the YAML file: + + ```yaml + env: + - name: SREVIEW_EVENT + value: '"foo"' + ``` + + ... will set the value of the `$event` configuration value to 'foo' + + The configuration values should be set as environment variables to the + `sreview-master` and the `sreview-web` deployments. +- Make sure to perform the following configuration steps: + - Create some buckets in an Amazon S3-compatible object store, and + store their access keys in the `s3_access_config` configuration + value, and configure the `inputglob`, `pubdir` and `outputdir` + configuration variables so they point to the correct buckets. If + you're using minikube to test this out, the `minio.yaml` file will + set up a pod for minio, which provides a single-container (i.e., + non-redundant) S3-compatible installation (see the logs of the + container it sets up for details on the authentication, and + optionally change them in the environment for the pod in + `minio.yaml`). Please note that this is not safe for production, + unless backups are taken of (at least) the data in the input bucket. + You'll still need to create the buckets manually; the yaml file + doesn't do that for you. + - Make sure the bucket that the `pubdir` configuration value points to + allows unauthenticated users to download the files there, and set the + `vid_prefix` configuration value so that it contains the URL for + read-only unauthenticated access to this bucket. For minio as + configured with the `minio.yaml` file, this URL + would be `http://sreview-storage:9000/bucketname`, with `bucketname` + replaced by the name of the bucket in question. + - Configure the URL that will be used by your SReview deployment in + the `urlbase` configuration value, so that `sreview-notify` knows + where to point to. +- Ensure that the user can access your SReview instance through a load + balancer or an ingress or something along those lines (whichever suits + your setup best). There is an example ingress configuration for + `sreview.example.com` in the `master.yaml` file, but this may not suit + your purpose. + +After that, just `kubectl apply` these files. --- sreview-0.5.0.orig/kubernetes/config.yaml +++ sreview-0.5.0/kubernetes/config.yaml @@ -0,0 +1,59 @@ +--- +# Before deploying, change the configuration in this ConfigMap and the +# Secret up next to something that suits your environment. +apiVersion: v1 +kind: ConfigMap +metadata: + name: sreview-config +data: +# For a list of possible values, run "sreview-config -a dump", which is +# a binary found in any of the SReview containers. +# +# To transform the names of variables as reported by sreview-config, +# transform them to upper case and prepend the literal string SREVIEW_ +# to them. +# +# The values should be encoded as JSON. This includes plain string +# values, which should be double-quoted (i.e., use '"foo"' if you want +# the string `foo` to be used), otherwise they're not JSON strings after +# the YAML parsing. +# +# You may add or remove as many variables as you like here, but note +# that the SREVIEW_DBISTRING, SREVIEW_SECRET, and SREVIEW_ADMINPW values +# are set in the "Secret", below. + SREVIEW_ADMINUSER: '"admin@example.com"' + SREVIEW_EVENT: '"test"' + SREVIEW_ANONREVIEWS: '1' + SREVIEW_URLBASE: '"http://sreview.example.com"' + SREVIEW_PREROLL_TEMPLATE: '"https://github.com/yoe/SReview/raw/master/t/testvids/just-title.svg"' + SREVIEW_POSTROLL_TEMPLATE: '"https://github.com/yoe/SReview/raw/master/t/testvids/just-title.svg"' + SREVIEW_ACCESSMETHODS: '{"input":"S3","intermediate":"S3","output":"S3"}' + SREVIEW_S3_ACCESS_CONFIG: '{"default":{"aws_access_key_id":"adminuser", "aws_secret_access_key":"adminpass","secure":0,"host":"sreview-storage:9000"}}' + SREVIEW_INPUTGLOB: '"input/*"' + SREVIEW_PUBDIR: '"inter"' + SREVIEW_OUTPUTDIR: '"output"' + SREVIEW_PARSE_RE: '"\\/(?[^-]+)(-(?[^-]+))?-(?\\d{4})(?\\d{2})(?\\d{2})(?\\d{2})(?\\d{2})(?\\d{2}).ts"' + SREVIEW_AUDIO_MULTIPLEX_MODE: '"astream"' + SREVIEW_VID_PREFIX: '"//minio.example.com/inter"' + SREVIEW_PREVIEW_EXTEN: '"mp4"' +--- +apiVersion: v1 +kind: Secret +metadata: + name: sreview-secret +type: Opaque +stringData: +# DO NOT remove these. They are assumed to always exist by the stuff +# in master.yaml +# +# If you don't want an admin user to be created upon first load of the +# webapp, then remove the SREVIEW_ADMINUSER variable above. Removing the +# SREVIEW_ADMINPW value below will make things fail. +# If you want to change the admin user's password after it has already +# been created, either destroy and recreate the database or change it +# through the admin interface. It is not possible to change it once the +# user has been created. + SREVIEW_ADMINPW: '"admin"' + SREVIEW_DBPASS: '' + SREVIEW_DBISTRING: '"dbi:Pg:dbname=sreview;host=sreview-database;user=sreview;password="' + SREVIEW_SECRET: '"_INSECURE_DEFAULT_REPLACE_ME_"' --- sreview-0.5.0.orig/kubernetes/master.yaml +++ sreview-0.5.0/kubernetes/master.yaml @@ -0,0 +1,215 @@ +--- +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: sreview-ingress +# annotations: +# nginx.ingress.kubernetes.io/rewrite-target: /$1 +spec: + rules: + - host: sreview.example.com + http: + paths: + - path: / + backend: + serviceName: sreview-web + servicePort: 8080 +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: sreview-database +spec: + replicas: 1 + selector: + matchLabels: + app: sreview-database + serviceName: "sreview-database" + template: + metadata: + labels: + app: sreview-database + spec: + containers: + - name: postgres + image: postgres:latest + env: + - name: PGDATA + value: "/var/lib/postgresql/data/db" + - name: POSTGRES_USER + value: sreview + - name: POSTGRES_DB + value: sreview + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: sreview-secret + key: SREVIEW_DBPASS + ports: + - containerPort: 5432 + name: postgresql + volumeMounts: + - mountPath: "/var/lib/postgresql/data" + name: "postgresdata" + volumes: + - name: "postgresdata" + persistentVolumeClaim: + claimName: "postgresdata" +--- +apiVersion: v1 +kind: Service +metadata: + name: sreview-database + labels: + app: sreview-database +spec: + ports: + - port: 5432 + name: postgresql + clusterIP: None + selector: + app: sreview-database +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sreview-web +spec: + replicas: 3 + selector: + matchLabels: + app: sreview-web + template: + metadata: + labels: + app: sreview-web + spec: + containers: + - name: web + image: registry.salsa.debian.org/debconf-video-team/sreview/web:master + imagePullPolicy: Always + envFrom: + - configMapRef: + name: sreview-config + env: + - name: SREVIEW_ADMINPW + valueFrom: + secretKeyRef: + name: sreview-secret + key: SREVIEW_ADMINPW + - name: SREVIEW_DBISTRING + valueFrom: + secretKeyRef: + name: sreview-secret + key: SREVIEW_DBISTRING + - name: SREVIEW_SECRET + valueFrom: + secretKeyRef: + name: sreview-secret + key: SREVIEW_SECRET +--- +apiVersion: v1 +kind: Service +metadata: + name: sreview-web +spec: + type: NodePort + selector: + app: sreview-web + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: sreview-master +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: manage-jobs +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "watch", "list"] +- apiGroups: ["batch", "extensions"] + resources: ["jobs"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: manage-jobs +subjects: +- kind: ServiceAccount + name: sreview-master + apiGroup: "" +roleRef: + kind: Role + name: manage-jobs + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sreview-master +spec: + replicas: 1 + selector: + matchLabels: + app: sreview-master + template: + metadata: + labels: + app: sreview-master + spec: + serviceAccountName: sreview-master + containers: + - name: master + image: registry.salsa.debian.org/debconf-video-team/sreview/master-kube:master + imagePullPolicy: Always + envFrom: + - configMapRef: + name: sreview-config + env: + - name: SREVIEW_DBISTRING + valueFrom: + secretKeyRef: + name: sreview-secret + key: SREVIEW_DBISTRING +--- +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: detect +spec: + concurrencyPolicy: Forbid + schedule: 0,30 * * * * + jobTemplate: + metadata: + labels: + app: sreview-detect + spec: + template: + metadata: + labels: + app: sreview-detect + spec: + restartPolicy: OnFailure + containers: + - name: detect + image: registry.salsa.debian.org/debconf-video-team/sreview/detect:master + imagePullPolicy: Always + command: ["/usr/bin/sreview-detect"] + envFrom: + - configMapRef: + name: sreview-config + env: + - name: SREVIEW_DBISTRING + valueFrom: + secretKeyRef: + name: sreview-secret + key: SREVIEW_DBISTRING --- sreview-0.5.0.orig/kubernetes/minio.yaml +++ sreview-0.5.0/kubernetes/minio.yaml @@ -0,0 +1,65 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: sreview-storage +spec: + replicas: 1 + selector: + matchLabels: + app: sreview-storage + serviceName: "sreview-storage" + template: + metadata: + labels: + app: sreview-storage + spec: + containers: + - name: minio + image: minio/minio + volumeMounts: + - mountPath: "/data" + name: "minio" + ports: + - containerPort: 9000 + name: minio + args: + - server + - "/data" + env: + - name: MINIO_ACCESS_KEY + value: adminuser + - name: MINIO_SECRET_KEY + value: adminpass + volumes: + - name: "minio" + persistentVolumeClaim: + claimName: "minio" +--- +apiVersion: v1 +kind: Service +metadata: + name: sreview-storage + labels: + app: sreview-storage +spec: + ports: + - port: 9000 + name: minio + clusterIP: None + selector: + app: sreview-storage +--- +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: sreview-storage +spec: + rules: + - host: minio.example.com + http: + paths: + - path: / + backend: + serviceName: sreview-storage + servicePort: 9000 --- sreview-0.5.0.orig/kubernetes/storage-minikube.yaml +++ sreview-0.5.0/kubernetes/storage-minikube.yaml @@ -0,0 +1,54 @@ +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: postgresdata + labels: + type: local +spec: + storageClassName: postgresdata + capacity: + storage: 100Mi + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/vda1/postgresdata" +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: minio + labels: + type: local +spec: + storageClassName: minio + capacity: + storage: 10Gi + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/vda1/minio" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgresdata +spec: + storageClassName: postgresdata + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: minio +spec: + storageClassName: minio + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi --- sreview-0.5.0.orig/lib/SReview/API/Helpers.pm +++ sreview-0.5.0/lib/SReview/API/Helpers.pm @@ -0,0 +1,26 @@ +package SReview::API::Helpers; + +use strict; +use warnings; + +use Exporter 'import'; +our @EXPORT_OK = qw/db_query/; + +use SReview::Config::Common; +use Mojo::JSON qw/decode_json/; + +sub db_query { + my ($dbh, $query, @args) = @_; + + my $st = $dbh->prepare($query); + $st->execute(@args); + my @results; + foreach my $row($st->fetchrow_arrayref) { + if(scalar(@{$row}) == 1) { + push @results, $row->[0]; + } else { + push @results, "[" . join(",", @$row) . "]"; + } + } + return decode_json("[" . join(",", @results) . "]"); +} --- sreview-0.5.0.orig/lib/SReview/Access.pm +++ sreview-0.5.0/lib/SReview/Access.pm @@ -0,0 +1,23 @@ +package SReview::Access; + +use Exporter; + +@ISA = qw(Exporter); +@EXPORT_OK = qw(admin_for); + +sub admin_for($$) { + my $c = shift; + my $talk = shift; + + if(exists($c->session->{admin})) { + return 1; + } + if(exists($c->session->{id})) { + my $st = $c->dbh->prepare("SELECT room FROM users WHERE id = ?"); + $st->execute($c->session->{id}); + my $row = $st->fetchrow_hashref; + if($talk->roomid = $row->{room}) { + return 1; + } + } +} --- sreview-0.5.0.orig/lib/SReview/Files/Collection/S3.pm +++ sreview-0.5.0/lib/SReview/Files/Collection/S3.pm @@ -0,0 +1,160 @@ +package SReview::Files::Access::S3; + +use Moose; +use File::Temp qw/tempfile tempdir mktemp/; +use File::Path qw/make_path/; +use File::Basename; +use DateTime::Format::ISO8601; +use Carp; + +extends 'SReview::Files::Access::Base'; + +has 's3object' => ( + is => 'ro', + required => 1, + isa => 'Net::Amazon::S3::Bucket', +); + +has '+filename' => ( + predicate => 'has_download', +); + +has 'workdir' => ( + is => 'ro', + lazy => 1, + builder => '_get_workdir', +); + +sub _get_workdir { + return tempdir(CLEANUP => 1); +} + +sub _get_file { + my $self = shift; + my @parts = split('\.', $self->relname); + my $ext = pop(@parts); + my $dir = $self->workdir; + + if($self->has_data) { + my ($fh, $file) = tempfile("s3-XXXXXX", dir => $dir, SUFFIX => ".$ext"); + $self->s3object->get_key_filename($self->relname, "GET", $file); + return $file; + } else { + my $file = join("/", $self->workdir, basename($self->relname)); + return $file; + } +} + +sub _probe_mtime { + my $self = shift; + my $meta = $self->s3object->head_key($self->relname); + + return DateTime::Format::ISO8601->parse_datetime($meta->{last_modified}); +} + +sub store_file { + my $self = shift; + return if(!$self->has_download); + + $self->s3object->add_key_filename($self->relname, $self->filename, {}) or croak($self->s3object->errstr); + + $self->stored; +} + +sub delete { + my $self = shift; + $self->s3object->delete_key($self->relname) +} + +sub valid_path_filename { + my $self = shift; + + my $path = join('/', $self->workdir, $self->relname); + make_path(dirname($path)); + symlink($self->filename, $path); + return $path; +} + +sub DESTROY { + my $self = shift; + $self->SUPER::DESTROY(); + if($self->has_download) { + unlink($self->filename); + } +} + +no Moose; + +package SReview::Files::Collection::S3; + +use Moose; +use Net::Amazon::S3; +use DateTime::Format::ISO8601; +use SReview::Config::Common; + +extends "SReview::Files::Collection::Base"; + +has 's3object' => ( + is => 'ro', + isa => 'Net::Amazon::S3::Bucket', + lazy => 1, + builder => '_probe_s3obj', +); + +has '+fileclass' => ( + default => 'SReview::Files::Access::S3', +); + +sub _probe_s3obj { + my $self = shift; + my $config = SReview::Config::Common::setup(); + my $bucket; + if($self->has_baseurl) { + $bucket = $self->baseurl; + } else { + my @elements = split('\/', $self->globpattern); + do { + $bucket = shift(@elements) + } while(!length($bucket)); + $self->_set_baseurl($bucket); + } + my $aconf = $config->get('s3_access_config'); + if(exists($aconf->{$bucket})) { + $aconf = $aconf->{$bucket}; + } else { + if(!exists($aconf->{default})) { + croak("S3 access configuration does not exist for $bucket, and nor does a default exist"); + } + $aconf = $aconf->{default}; + } + return Net::Amazon::S3->new($aconf)->bucket($bucket); +} + +sub _probe_children { + my $self = shift; + my $return = []; + my $baseurl; + + foreach my $key(@{$self->s3object->list_all->{keys}}) { + push @$return, SReview::Files::Access::S3->new( + s3object => $self->s3object, + baseurl => $self->baseurl, + mtime => DateTime::Format::ISO8601->parse_datetime($key->{last_modified}), + relname => $key->{key}, + ); + } + return $return; +} + +sub _create { + my $self = shift; + my %options = @_; + + $options{s3object} = $self->s3object; + + return $self->SUPER::_create(%options); +} + +no Moose; + +1; --- sreview-0.5.0.orig/lib/SReview/Files/Factory.pm +++ sreview-0.5.0/lib/SReview/Files/Factory.pm @@ -0,0 +1,334 @@ +package SReview::Files::Base; + +use Moose; + +has 'is_collection' => ( + isa => 'Bool', + is => 'ro', +); + +no Moose; + +package SReview::Files::Access::Base; + +use Moose; +use DateTime; +use Carp; + +extends 'SReview::Files::Base'; + +has '+is_collection' => ( + default => 0, +); + +has 'relname' => ( + is => 'rw', + isa => 'Str', + required => 1, +); + +has 'filename' => ( + isa => 'Str', + is => 'ro', + lazy => 1, + builder => '_get_file', +); + +has 'mtime' => ( + isa => 'DateTime', + is => 'ro', + lazy => 1, + builder => '_probe_mtime', +); + +has 'baseurl' => ( + isa => 'Str', + is => 'ro', + required => 1, +); + +has 'url' => ( + isa => 'Str', + is => 'ro', + lazy => 1, + builder => '_probe_url', +); + +has 'create' => ( + is => 'rw', + traits => ['Bool'], + isa => 'Bool', + default => 0, + required => 1, + handles => { + has_data => 'not', + }, +); + +has 'is_stored' => ( + is => 'ro', + isa => 'Bool', + traits => ['Bool'], + default => 0, + handles => { + auto_save => 'unset', + no_auto_save => 'set', + stored => 'set', + }, +); + +sub _probe_url { + my $self = shift; + + return join('/', $self->baseurl, $self->relname); +} + +sub DESTROY { + my $self = shift; + if($self->create) { + if(!$self->is_stored) { + carp "object destructor for '" . $self->url . "' entered without an explicit store, storing now..."; + $self->store_file; + } + } +} + +no Moose; + +package SReview::Files::Access::direct; + +use Moose; +use DateTime; +use File::Path qw/make_path/; +use File::Basename qw/dirname/; + +extends 'SReview::Files::Access::Base'; + +sub _get_file { + my $self = shift; + + if($self->create) { + make_path(dirname($self->url)); + unlink($self->url); + } + return $self->url; +} + +sub store_file { + my $self = shift; + $self->stored; + return 1; +} + +sub _probe_mtime { + my $self = shift; + my @stat = stat($self->filename); + + return DateTime->from_epoch(epoch => $stat[9]); +} + +sub delete { + my $self = shift; + + unlink($self->url); +} + +sub valid_path_filename { + my $self = shift; + + return $self->url; +} + +no Moose; + +package SReview::Files::Collection::Base; + +use Moose; +use Carp; + +extends 'SReview::Files::Base'; + +has '+is_collection' => ( + default => 1, +); + +has 'children' => ( + isa => 'ArrayRef[SReview::Files::Base]', + traits => ['Array'], + is => "ro", + lazy => 1, + handles => { + sorted_files => 'sort', + }, + builder => '_probe_children', +); + +has 'baseurl' => ( + isa => 'Str', + is => 'ro', + predicate => 'has_baseurl', + writer => '_set_baseurl', + lazy => 1, + builder => '_probe_baseurl', +); + +has 'globpattern' => ( + isa => 'Str', + is => 'ro', + predicate => 'has_globpattern', + lazy => 1, + builder => '_probe_globpattern', +); + +has 'fileclass' => ( + isa => 'Str', + is => 'ro', + required => 1, +); + +sub _probe_baseurl { + my $self = shift; + + if(!$self->has_globpattern) { + croak("either a globpattern or a baseurl are required!\n"); + } + @_ = split(/\*/, $self->globpattern); + + my $rv = $_[0]; + while(substr($rv, -1) eq '/') { + substr($rv, -1) = ''; + } + + return $rv; +} + +sub _probe_globpattern { + my $self = shift; + + if(!$self->has_baseurl) { + croak("either a globpattern or a baseurl are required!\n"); + } + return join('/', $self->baseurl, '*'); +} + +sub _create { + my $self = shift; + my %options = @_; + + if(exists($options{fullname})) { + if(substr($options{fullname}, 0, length($self->baseurl)) ne $self->baseurl) { + croak($options{fullname} . " is not accessible through this collection"); + } + $options{relname} = substr($options{fullname}, length($self->baseurl)); + while(substr($options{relname}, 0, 1) eq '/') { + $options{relname} = substr($options{relname}, 1); + } + delete $options{fullname}; + } + + $options{baseurl} = $self->baseurl; + + my $fileclass = $self->fileclass; + + return "$fileclass"->new(%options); +} + +sub get_file { + my $self = shift; + my %options = @_; + + $options{create} = 0; + + return $self->_create(%options); +} + +sub add_file { + my $self = shift; + my %options = @_; + + $options{create} = 1; + + return $self->_create(%options); +} + +sub has_file { + my $self = shift; + my $target = shift; + + return scalar(grep({(!$_->is_collection) && ($_->relname eq $target)} @{$self->children})); +} + +no Moose; + +package SReview::Files::Collection::direct; + +use Moose; +use File::Basename; +use Carp; + +extends 'SReview::Files::Collection::Base'; + +has '+fileclass' => ( + default => 'SReview::Files::Access::direct', +); + +sub _probe_children { + my $self = shift; + my @return; + + foreach my $file(glob($self->globpattern)) { + my $child; + if(-d $file) { + $child = SReview::Files::Collection::direct->new(baseurl => join("/", $self->baseurl, basename($file))); + } else { + my $basename = substr($file, length($self->baseurl)); + while(substr($basename, 0, 1) eq '/') { + $basename = substr($basename, 1); + } + $child = SReview::Files::Access::direct->new(baseurl => $self->baseurl, relname => $basename); + } + push @return, $child; + } + + return \@return; +} + +sub has_file { + my ($self, $target) = @_; + + if(-f join('/', $self->baseurl, $target)) { + return 1; + } + return 0; +} + +no Moose; + +package SReview::Files::Factory; + +use SReview::Config::Common; + +sub create { + my $class = shift; + my $target = shift; + my $relname = shift; + my $config = SReview::Config::Common::setup(); + + my $methods = $config->get("accessmethods"); + my $method; + if(!exists($methods->{$target})) { + die "missing method for $target\n"; + } + $method = $methods->{$target}; + eval "require SReview::Files::Collection::$method;"; + if($@) { + die "$@: $!"; + } + if($target eq "input") { + return "SReview::Files::Collection::$method"->new(globpattern => $relname); + } else { + return "SReview::Files::Collection::$method"->new(baseurl => $relname); + } +} + +1; --- sreview-0.5.0.orig/lib/SReview/Job.pm +++ sreview-0.5.0/lib/SReview/Job.pm @@ -0,0 +1,61 @@ +package SReview::Job; + +use SReview::Config; +use Scalar::Util qw(weaken); +use Carp; + +my $singleton; + +sub new { + my $class = shift; + if(defined $singleton) { + croak "$class object created twice!"; + } + my $self = {}; + $self->{jobname} = shift; + $self->{talkid} = shift; + $self->{dbh} = shift; + die "Missing arguments!" unless defined($dbh); + $self->{jobs} = []; + $dbh->begin; + my $set = $dbh->prepare("UPDATE talks SET progress='running' WHERE id = ?"); + $set->execute($talkid); + $dbh->commit; + bless $self, $class; + $singleton = $self; + weaken($singleton); + return $self; +}; + +$SIG{'__DIE__'} = sub { + my $msg = shift; + my $ref = $singleton; + if(!defined($ref)) { + die $msg; + } + my $insert = $ref->{dbh}->prepare('INSERT INTO logs(talkid, job, message) VALUES(?, ?, ?)'); + $ref->{dbh}->begin_work(); + $insert->execute($ref->{jobname}, $ref->{talkid}, $msg); + $ref->{dbh}->commit(); +}; + +sub add_job { + my $self = shift; + push @{$self->{jobs}}, shift; +}; + +sub DESTROY { + my $self = shift; + my $progress = $dbh->prepare('UPDATE talks SET perc = (?/?)*100 WHERE id = ?'); + my $total = scalar(@{$self->{jobs}}); + for(my $i=0; $i<$total; $i++) { + $dbh->begin; + $progress->execute($i, $total, $talkid); + my $job = ${$self->{jobs}[$i]}; + print $job; + system($job); + $dbh->commit; + } +}; + +1; --- sreview-0.5.0.orig/lib/SReview/Model/DbElement.pm +++ sreview-0.5.0/lib/SReview/Model/DbElement.pm @@ -0,0 +1,34 @@ +package SReview::Model::DbElement; + +use Moose; + +use SReview::Config::Common; +use SReview::Db; + +has 'config' => ( + is => 'rw', + isa => 'SReview::Config', + builder => '_get_config', + lazy => 1, +); + +sub _get_config { + my $self = shift; + return SReview::Config::Common::setup; +} + +has 'dbh' => ( + is => 'rw', + isa => 'Mojo::Pg', + builder => '_get_dbh', + lazy => 1, +); + +sub _get_dbh { + my $self = shift; + my $pg = Mojo::Pg->new()->dsn($self->config->get('dbistring')); +} + +no Moose; + +1; --- sreview-0.5.0.orig/lib/SReview/Model/Event.pm +++ sreview-0.5.0/lib/SReview/Model/Event.pm @@ -0,0 +1,38 @@ +package SReview::Model::Event; + +use Moose; + +extends 'SReview::Model::DbElement'; + +has 'name' => ( + is => 'ro', + builder => '_get_name', +); + +sub _get_name { + return shift->config->get('event'); +} + +has 'inputdir' => ( + is => 'ro', + builder => '_get_inputdir', + lazy => 1, +); + +sub _get_inputdir { + my $self = shift; + my $st = $self->dbh->db->dbh->prepare("SELECT inputdir FROM events WHERE name = ?"); + $st->execute($self->name); + if($st->rows == 0) { + return $self->name; + } + my $dir = $st->fetchrow_hashref()->{inputdir}; + if(!defined($dir)) { + return $self->name; + } + return $dir; +} + +no Moose; + +1; --- sreview-0.5.0.orig/lib/SReview/Talk.pm +++ sreview-0.5.0/lib/SReview/Talk.pm @@ -0,0 +1,754 @@ +package SReview::Talk; + +use Moose; +use Mojo::Pg; +use Mojo::Template; +use SReview::Config::Common; +use SReview::Talk::State; + +my $config = SReview::Config::Common::setup; +my $pg = Mojo::Pg->new->dsn($config->get('dbistring')) or die "Cannot connect to database!"; + +=head1 NAME + +SReview::Talk - Database abstraction for talks in the SReview database + +=head1 SYNOPSIS + + use SReview::Talk; + + my $talk = SReview::Talk->new(talkid => 1); + print $talk->nonce; + my $nonce = $talk->nonce; + my $talk_alt = SReview::Talk->by_nonce($nonce); + print $talk_alt->talkid; # 1 + + $talk->add_correction(length_adj => 1); + $talk->done_correcting; + +=head1 DESCRIPTION + +SReview::Talk provides a (Moose-based) object-oriented interface to the +data related to a talk that is stored in the SReview database. Although +it is not yet used everywhere, the intention is for it to eventually +replace all the direct PostgreSQL calls. + +=head1 PROPERTIES + +=head2 talkid + +The unique ID of the talk. Required attribute at construction time (but +see the C method, below). Is used to look up the relevant data +in the database. + +=cut + +has 'talkid' => ( + required => 1, + is => 'ro', + trigger => sub { + my $self = shift; + my $val = shift; + my $st = $pg->db->dbh->prepare("SELECT count(*) FROM talks WHERE id = ?"); + $st->execute($val); + die "Talk does not exist.\n" unless $st->rows == 1; + }, +); + +=head2 pathinfo + +Helper property to look up information from the database. Should not be +used directly. + +=cut + +has 'pathinfo' => ( + lazy => 1, + is => 'bare', + builder => '_load_pathinfo', + reader => '_get_pathinfo', +); + +sub _load_pathinfo { + my $self = shift; + + my $pathinfo = {}; + + my $eventdata = $pg->db->dbh->prepare("SELECT events.id AS eventid, events.name AS event, rooms.name AS room, rooms.outputname AS room_output, rooms.id AS room_id, talks.starttime::date AS date, to_char(starttime, 'DD Month yyyy at HH:MI') AS readable_date, to_char(starttime, 'yyyy') AS year, talks.slug, talks.title, talks.subtitle, talks.state, talks.nonce, talks.apologynote FROM talks JOIN events ON talks.event = events.id JOIN rooms ON rooms.id = talks.room WHERE talks.id = ?"); + $eventdata->execute($self->talkid); + my $row = $eventdata->fetchrow_hashref(); + + $pathinfo->{"workdir"} = join('/', $row->{eventid}, $row->{date}, substr($row->{room}, 0, 1)); + + my @elements = ($config->get('outputdir')); + foreach my $element(@{$config->get('output_subdirs')}) { + push @elements, $row->{$element}; + } + $pathinfo->{"finaldir"} = join('/', @elements); + + $pathinfo->{"slug"} = $row->{"slug"}; + + $pathinfo->{"raw"} = $row; + + return $pathinfo; +} + +=head2 apology + +The apology note, if any. Predicate: C. + +=cut + +has 'apology' => ( + lazy => 1, + is => 'rw', + builder => '_load_apology', + clearer => 'clear_apology', + predicate => 'has_apology', +); + +sub _load_apology { + return shift->_get_pathinfo->{raw}{apologynote}; +} + +=head2 comment + +The comments that the user entered in the "other brokenness" field. +Predicate: C; clearer: C. + +=cut + +has 'comment' => ( + lazy => 1, + is => 'rw', + builder => '_load_comment', + clearer => 'clear_comment', + predicate => 'has_comment', +); + +sub _load_comment { + my $self = shift; + my $st = $pg->db->dbh->prepare("SELECT comments FROM talks WHERE id = ?"); + $st->execute($self->talkid); + my $row = $st->fetchrow_hashref; + return $row->{comments}; +} + +=head2 corrected_times + +The start- and endtime of the talk, with corrections (if any) applied. + +=cut + +has 'corrected_times' => ( + lazy => 1, + is => 'ro', + builder => '_load_corrected_times', +); + +sub _load_corrected_times { + my $self = shift; + + my $times = {}; + + my $st = $pg->db->dbh->prepare("SELECT starttime, to_char(starttime, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS isostart, endtime, to_char(endtime, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS isoend from talks WHERE id = ?"); + + $st->execute($self->talkid); + + die "talk lost" unless $st->rows > 0; + + my $row = $st->fetchrow_hashref(); + $times->{start} = $row->{starttime}; + $times->{end} = $row->{endtime}; + $times->{start_iso} = $row->{isostart}; + $times->{end_iso} = $row->{isoend}; + + $st = $pg->db->dbh->prepare("SELECT coalesce(talks.starttime + (corrections.property_value || ' seconds')::interval, talks.starttime) AS corrected_time, to_char(coalesce(talks.starttime + (corrections.property_value || ' seconds')::interval, talks.starttime), 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS isotime FROM talks LEFT JOIN corrections ON talks.id = corrections.talk LEFT JOIN properties ON properties.id = corrections.property WHERE talks.id = ? AND properties.name = 'offset_start'"); + $st->execute($self->talkid); + if($st->rows > 0) { + $row = $st->fetchrow_hashref(); + $times->{start} = $row->{corrected_time}; + $times->{start_iso} = $row->{isotime}; + } + $st = $pg->db->dbh->prepare("SELECT corrected_time, to_char(corrected_time, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') AS isotime FROM (select ?::timestamptz + (talks.endtime - talks.starttime) + (coalesce(corrections.property_value, '0') || ' seconds')::interval AS corrected_time FROM talks LEFT JOIN corrections ON talks.id = corrections.talk LEFT JOIN properties ON properties.id = corrections.property WHERE talks.id = ? AND properties.name = 'length_adj') AS sq"); + $st->execute($times->{start}, $self->talkid); + if($st->rows > 0) { + $row = $st->fetchrow_hashref(); + $times->{end} = $row->{corrected_time}; + $times->{end_iso} = $row->{isotime}; + } + return $times; +} + +=head2 nonce + +The talk's unique hex string, used to look it up for review. + +=cut + +has 'nonce' => ( + is => 'rw', + builder => '_load_nonce', + lazy => 1, +); + +sub _load_nonce { + return shift->_get_pathinfo->{raw}{nonce}; +} + +=head2 date + +The date on which the talk happened + +=cut + +has 'date' => ( + lazy => 1, + is => 'rw', + builder => '_load_date', +); + +sub _load_date { + return shift->_get_pathinfo->{raw}{date}; +} + +=head2 readable_date + +The date on which the talk happened, in a (somewhat) more human-readable +format than the C property. + +=cut + +has 'readable_date' => ( + lazy => 1, + is => 'rw', + builder => '_load_readable_date', +); + +sub _load_readable_date { + return shift->_get_pathinfo->{raw}{readable_date}; +} + +=head2 eventname + +The name of the event of which this talk is part + +=cut + +has 'eventname' => ( + lazy => 1, + is => 'ro', + builder => '_load_eventname', +); + +sub _load_eventname { + my $self = shift; + return $self->_get_pathinfo->{raw}{event}; +} + +=head2 state + +The current state of the talk, as an L + +=cut + +has 'state' => ( + lazy => 1, + is => 'rw', + isa => 'SReview::Talk::State', + builder => '_load_state', +); + +sub _load_state { + my $self = shift; + return SReview::Talk::State->new($self->_get_pathinfo->{raw}{state}); +} + +=head2 title + +The title of the talk + +=cut + +has 'title' => ( + lazy => 1, + is => 'rw', + builder => '_load_title', +); + +sub _load_title { + my $self = shift; + return $self->_get_pathinfo->{raw}{title}; +} + +=head2 workdir + +The working directory where the files for this talk should be stored + +=cut + +has 'workdir' => ( + lazy => 1, + is => 'rw', + builder => '_load_workdir', +); + +sub _load_workdir { + my $self = shift; + return join('/', $config->get("pubdir"), $self->_get_pathinfo->{"workdir"}); +} + +=head2 relative_name + +The relative path- and file name under the output directory for this +talk. + +=cut + +has 'relative_name' => ( + lazy => 1, + is => 'rw', + builder => '_load_relative_name', +); + +sub _load_relative_name { + my $self = shift; + return join('/', $self->_get_pathinfo->{"workdir"}, $self->_get_pathinfo->{'slug'}); +} + +=head2 outname + +The output name for this talk + +=cut + +has 'outname' => ( + lazy => 1, + is => 'rw', + builder => '_load_outname', +); + +sub _load_outname { + my $self = shift; + return join('/', $self->workdir, $self->_get_pathinfo->{"slug"}); +} + +=head2 finaldir + +The directory in which things are stored + +=cut + +has 'finaldir' => ( + lazy => 1, + is => 'rw', + builder => '_load_finaldir', +); + +sub _load_finaldir { + my $self = shift; + return $self->_get_pathinfo->{"finaldir"}; +} + +=head2 slug + +A short, safe representation of the talk; used for filenames. + +=cut + +has 'slug' => ( + lazy => 1, + is => 'rw', + builder => '_load_slug', +); + +sub _load_slug { + my $self = shift; + return $self->_get_pathinfo->{"slug"}; +} + +=head2 corrections + +The corrections that are set on this talk. + +Supports: + +=over + +=item has_correction + +check whether a correction exists (by name) + +=item set_correction + +Overwrite a correction with a new value + +=item clear_correction + +Remove a correction from the set of corrections + +=item correction_pairs + +Get a key/value list of corrections + +=back + +=cut + +has 'corrections' => ( + traits => ['Hash'], + isa => 'HashRef[Str]', + lazy => 1, + is => 'rw', + builder => '_load_corrections', + clearer => '_clear_corrections', + handles => { + has_correction => 'exists', + set_correction => 'set', + clear_correction => 'delete', + correction_pairs => 'kv', + }, +); + +sub _load_corrections { + my $self = shift; + + my $corrections_data = $pg->db->dbh->prepare("SELECT corrections.talk, properties.name AS property, corrections.property_value FROM corrections LEFT JOIN properties ON corrections.property = properties.id WHERE talk = ?"); + $corrections_data->execute($self->talkid); + + my %corrections; + + while(my $row = $corrections_data->fetchrow_hashref()) { + my $name = $row->{property}; + my $val = $row->{property_value}; + $corrections{$name} = $val; + } + + foreach my $prop ("offset_start", "length_adj", "offset_audio") { + if(!exists($corrections{$prop})) { + $corrections{$prop} = 0; + } + } + + if(!exists($corrections{audio_channel})) { + $corrections{audio_channel} = 0; + } + + return \%corrections; +} + +=head2 video_fragments + +Gets a list of hashes with data on the fragments of video files that are +necessary to build the talk, given the schedule and the current +corrections. + +Each hash contains: + +=over + +=item talkid + +The talk ID for fragments that are part of the main video; -1 for +fragments that are part of the pre video; and -2 for fragments that are +part of the post video. + +=item rawid + +The unique ID of the raw file + +=item raw_filename + +The filename of the raw file + +=item fragment_start + +The offset into the raw file where the interesting content begins. + +=item raw_length + +The length of the entire video (should be the same for each fragment) + +=item raw_length_corrected + +The length of the interesting content in I raw file + +=back + +=cut + +has 'video_fragments' => ( + lazy => 1, + is => 'rw', + builder => '_load_video_fragments', +); + +sub _load_video_fragments { + my $self = shift; + my $corrections = $self->corrections; + + my $talk_data = $pg->db->dbh->prepare("SELECT talkid, rawid, raw_filename, extract(epoch from fragment_start) AS fragment_start, extract(epoch from raw_length) as raw_length, extract(epoch from raw_length_corrected) as raw_length_corrected FROM adjusted_raw_talks(?, make_interval(secs :=?::numeric), make_interval(secs := ?::numeric)) ORDER BY talk_start, raw_start"); + $talk_data->execute($self->talkid, $corrections->{"offset_start"}, $corrections->{"length_adj"}); + + my $rows; + while(my $row = $talk_data->fetchrow_hashref()) { + push @$rows, $row; + } + + return $rows; +} + +=head2 speakers + +The names of the speakers as a single string, in the format 'Firstname +Lastname, Firstname Lastname, ..., Firstname Lastname and Firstname +Lastname' + +=cut + +has 'speakers' => ( + lazy => 1, + is => 'ro', + builder => '_load_speakers', +); + +sub _load_speakers { + my $self = shift; + + my $spk = $pg->db->dbh->prepare("SELECT speakerlist(?)"); + + $spk->execute($self->talkid); + + my $row = $spk->fetchrow_arrayref; + + return $row->[0]; +} + +=head2 speakerlist + +An array of speaker names + +=cut + +has 'speakerlist' => ( + lazy => 1, + is => 'ro', + isa => 'ArrayRef[Str]', + builder => '_load_speakerlist', +); + +sub _load_speakerlist { + my $self = shift; + + my $query = $pg->db->dbh->prepare("SELECT speakers.name FROM speakers JOIN speakers_talks ON speakers.id = speakers_talks.speaker WHERE speakers_talks.talk = ?"); + + $query->execute($self->talkid); + + my $rv = []; + + while(my $talk = $query->fetchrow_arrayref) { + push @$rv, $talk->[0]; + } + + return $rv; +} + +=head2 room + +The room in which the talk happened/will happen + +=cut + +has 'room' => ( + lazy => 1, + is => 'rw', + builder => '_load_room', +); + +sub _load_room { + return shift->_get_pathinfo->{raw}{room}; +} + +=head2 roomid + +The unique ID of the room + +=cut + +has 'roomid' => ( + lazy => 1, + is => 'rw', + builder => '_load_roomid', +); + +sub _load_roomid { + return shift->_get_pathinfo->{raw}{room_id} +} + +=head2 eventurl + +The URL for the talk on the event's website. Only contains data if +C<$eventurl_format> is set in the config file; if it doesn't, returns +the empty string. + +=cut + +has 'eventurl' => ( + lazy => 1, + is => 'rw', + builder => '_load_eventurl', +); + +sub _load_eventurl { + my $self = shift; + my $mt = Mojo::Template->new; + if(defined($config->get('eventurl_format'))) { + return $mt->vars(1)->render($config->get('eventurl_format'), { + slug => $self->slug, + room => $self->room, + date => $self->date, + event => $self->eventname, + year => $self->_get_pathinfo->{raw}{year}}); + } + return ""; +} + +=head2 + +The file extension of the preview file (.webm or .mp4) + +=cut + +has 'preview_exten' => ( + lazy => 1, + is => 'ro', + builder => '_load_preview_exten', +); + +# TODO: autodetect this, rather than hardcoding it +sub _load_preview_exten { + return $config->get('preview_exten'); +} + +=head1 METHODS + +=head2 by_nonce + +Looks up (and returns) the talk by nonce, rather than by talk ID + +=cut + +sub by_nonce { + my $klass = shift; + my $nonce = shift; + + my $st = $pg->db->dbh->prepare("SELECT * FROM talks WHERE nonce = ?"); + $st->execute($nonce); + die "Talk does not exist.\n" unless $st->rows == 1; + my $row = $st->fetchrow_arrayref; + my $rv = SReview::Talk->new(talkid => $row->[0], nonce => $nonce); + return $rv; +} + +=head2 add_correction + +Interpret a correction as a number, and add the passed parameter to it. +The new value of the correction will be the sum of the parameter and the +old correction. + +=cut + +sub add_correction { + my $self = shift; + my $corrname = shift; + my $value = shift; + + if($self->has_correction($corrname)) { + $value = $self->corrections->{$corrname} + $value; + } + $self->set_correction($corrname, $value); +} + +=head2 done_correcting + +Commit the created corrections to the database. Also commits other +things, like the comment. + +=cut + +sub done_correcting { + my $self = shift; + + my $db = $pg->db->dbh; + my $st = $db->prepare("INSERT INTO corrections(talk, property, property_value) VALUES (?, (SELECT id FROM properties WHERE name = ?), ?)"); + + $self->add_correction(serial => 1); + my $corrs = $self->corrections; + my $start = $corrs->{offset_start}; + my $end = $corrs->{offset_end}; + $start = 0 unless defined $start; + $end = 0 unless defined $end; + $self->set_correction(length_adj => $end - $start); + foreach my $pair($self->correction_pairs) { + $st->execute($self->talkid, $pair->[0], $pair->[1]); + } + if($self->has_comment) { + $db->prepare("UPDATE talks SET comments=? WHERE id = ?")->execute($self->comment, $self->talkid); + } else { + $db->prepare("UPDATE talks SET comments = NULL WHERE id = ?")->execute($self->talkid); + } + if($self->has_apology) { + $db->prepare("UPDATE talks SET apologynote=? WHERE id = ?")->execute($self->apology, $self->talkid); + } else { + $db->prepare("UPDATE talks SET apologynote = NULL WHERE id = ?")->execute($self->talkid); + } +} + +=head2 set_state + +Override the state of the talk to a new state, ignoring the state +transitions. Note, does not update the object, so this should be done +just before destroying it. + +=cut + +sub set_state { + my $self = shift; + my $newstate = shift; + + my $st = $pg->db->dbh->prepare("UPDATE talks SET state=?, progress='waiting' WHERE id=?"); + $st->execute($newstate, $self->talkid); +} + +=head2 state_done + +Set the progress to "done" in the given state. Does nothing if the talk +has since moved to another state. + +=cut + +sub state_done { + my $self = shift; + my $state = shift; + + my $st = $pg->db->dbh->prepare("UPDATE talks SET progress='done' WHERE state = ? AND id = ?"); + $st->execute($state, $self->talkid); +} + +=head2 reset_corrections + +Clear all corrections, except the serial one. Used when a user requests +that the talk be reset to default. + +=cut + +sub reset_corrections { + my $self = shift; + + $self->add_correction(serial => 1); + $pg->db->dbh->prepare("DELETE FROM corrections WHERE talk = ? AND property NOT IN (SELECT id FROM properties WHERE name = 'serial')")->execute($self->talkid) or die $!; +} + +no Moose; + +1; --- sreview-0.5.0.orig/lib/SReview/Talk/State.pm +++ sreview-0.5.0/lib/SReview/Talk/State.pm @@ -0,0 +1,47 @@ +package SReview::Talk::State; + +use overload '<=>' => 'statecmp', 'cmp' => 'statecmp', '""' => 'output'; + +use Carp; + +my %states = ( + waiting_for_files => 0, + cutting => 1, + generating_previews => 2, + notification => 3, + preview => 4, + transcoding => 5, + uploading => 6, + announcing => 7, + done => 8, + broken => 9, + needs_work => 10, + lost => 11, + ignored => 12, +); + +sub new { + my $class = shift; + my $val = shift; + croak "Unknown talk state value: $val" unless exists($states{$val}); + return bless \$val, $class; +} + +sub statecmp { + my $self = shift; + my $other = shift; + my $swapped = shift; + + croak "Unknown talk state value: $other" unless exists($states{$other}); + + if($swapped) { + return $states{$other} <=> $states{$$self}; + } else { + return $states{$$self} <=> $states{$other}; + } +} + +sub output { + my $self = shift; + return $$self; +} --- sreview-0.5.0.orig/lib/SReview/Video/Profile/DebConf.pm +++ sreview-0.5.0/lib/SReview/Video/Profile/DebConf.pm @@ -0,0 +1,18 @@ +use SReview::Video::ProfileFactory; +package SReview::Video::Profile::DebConf; + +use Moose; + +extends 'SReview::Video::Profile::mpeg2'; + +sub _probe_videobitrate { + return "1800"; +} + +sub speed { + return 4; +} + +no Moose; + +1; --- sreview-0.5.0.orig/lib/SReview/Video/Profile/mp4.pm +++ sreview-0.5.0/lib/SReview/Video/Profile/mp4.pm @@ -0,0 +1,20 @@ +use SReview::Video::ProfileFactory; +package SReview::Video::Profile::mp4; + +use Moose; + +extends 'SReview::Video::Profile::Base'; + +sub _probe_exten { + return 'mp4'; +} + +sub _probe_videocodec { + return "h264"; +} + +sub _probe_audiocodec { + return "aac"; +} + +1; --- sreview-0.5.0.orig/lib/SReview/Video/Profile/mpeg2.pm +++ sreview-0.5.0/lib/SReview/Video/Profile/mpeg2.pm @@ -0,0 +1,20 @@ +use SReview::Video::ProfileFactory; +package SReview::Video::Profile::mpeg2; + +use Moose; + +extends 'SReview::Video::Profile::Base'; + +sub _probe_exten { + return 'mpg'; +} + +sub _probe_videocodec { + return "mpeg2video"; +} + +sub _probe_audiocodec { + return "mp2"; +} + +1; --- sreview-0.5.0.orig/lib/SReview/Web/Controller/Admin.pm +++ sreview-0.5.0/lib/SReview/Web/Controller/Admin.pm @@ -0,0 +1,39 @@ +package SReview::Web::Controller::Admin; + +use Mojo::Base 'Mojolicious::Controller'; +use Mojo::Collection 'c'; + +sub main { + my $c = shift; + my $st; + my $talks = (); + my $room; + my $lastroom = ''; + + if(defined($c->session->{room})) { + $st = $c->dbh->prepare('SELECT id, room, name, starttime, speakers, state FROM talk_list WHERE eventid = ? AND roomid = ? ORDER BY starttime'); + $st->execute($c->eventid, $c->session->{room}); + } else { + $st = $c->dbh->prepare('SELECT id, room, name, starttime, speakers, state FROM talk_list WHERE eventid = ? ORDER BY room, starttime'); + $st->execute($c->eventid); + } + while(my $row = $st->fetchrow_hashref("NAME_lc")) { + if ($row->{'room'} ne $lastroom) { + if(defined($room)) { + push @$talks, c($lastroom => $room); + } + $room = []; + } + $lastroom = $row->{'room'}; + next unless defined($row->{id}); + push @$room, [$row->{'starttime'} . ': ' . $row->{'name'} . ' by ' . $row->{'speakers'} . ' (' . $row->{'state'} . ')' => $row->{'id'}]; + } + if(defined($room)) { + push @$talks, c($lastroom => $room); + } + $c->stash(email => $c->session->{email}); + $c->stash(talks => $talks); + $c->render; +} + +1; --- sreview-0.5.0.orig/lib/SReview/Web/Controller/Event.pm +++ sreview-0.5.0/lib/SReview/Web/Controller/Event.pm @@ -0,0 +1,63 @@ +package SReview::Web::Controller::Event; + +use Mojo::Base 'Mojolicious::Controller'; +use SReview::API::Helpers qw/db_query/; + +sub create { + my $c = shift; + + my $name = $c->stash("name"); + my $inputdir = $c->stash("inputdir"); + my $outputdir = $c->stash("outputdir"); + my $offset = $c->stash("time_offset"); + + if(!$c->auth_scope("api/event/rw")) { + $c->res->code(403); + $c->render('Unauthorized'); + return 0; + } + + $c->render(json => db_query($c->dbh, "INSERT INTO events(name, time_offset, inputdir, outputdir) VALUES(?,?,?,?) RETURNING json_build_object('id', \"id\")", $name, $offset, $inputdir, $outputdir)); +} + +sub by_title { + my $c = shift; + + $c->render(json => db_query($c->dbh, "SELECT row_to_json(events.*) FROM events WHERE title = ?", $c->stash("title"))); +} + +sub by_id { + my $c = shift; + + $c->render(json => db_query($c->dbh, "SELECT row_to_json(events.*) FROM events WHERE id = ?", $c->stash("id"))); +} + +sub update { + my $c = shift; + + if(!$c->auth_scope("api/event/rw")) { + $c->res->code(403); + $c->render('Unauthorized'); + return 0; + } + $c->render(json => db_query($c->dbh, "UPDATE events SET name = ?, inputdir = ?, outputdir = ?, time_offset = ? WHERE id = ?", $c->stash("name"), $c->stash("inputdir"), $c->stash("outputdir"), $c->stash("time_offset"), $c->stash("id"))); +} + +sub delete { + my $c = shift; + + if(!$c->auth_scope("api/event/rw")) { + $c->res->code(403); + $c->render('Unauthorized'); + return 0; + } + $c->render(json => db_query($c->dbh, "DELETE FROM events WHERE id = ?", $c->stash("id"))); +} + +sub list { + my $c = shift; + + $c->render(json => db_query($c->dbh, "SELECT row_to_json(events.*) FROM events")); +} + +1; --- sreview-0.5.0.orig/lib/SReview/Web/Controller/Keys.pm +++ sreview-0.5.0/lib/SReview/Web/Controller/Keys.pm @@ -0,0 +1,64 @@ +package SReview::Web::Controller::Keys; + +use Net::SSH::AuthorizedKeysFile; +use Net::SSH::AuthorizedKey; +use File::Basename qw(dirname); +use SReview::Config::C + +use Mojo::Base 'Mojolicious::Controller'; + +has 'event'; +has 'dbh'; + +sub eventpath { + my $self = shift; + my $st = $self->dbh->prepare('SELECT pathname FROM event WHERE name = ?'); + $st->execute($self->event); + if($st->rows == 0) { + return undef; + } + my $row = $st->fetchrow_hashref; + return $row->{pathname}; +} + +sub add { + my $self = shift; + my $key = shift; + + my $keysfile = Net::SSH::AuthorizedKeysFile->new(); + if (! -f $keysfile->path_locate()) { + open KEYS, ">>", $keysfile->path_locate(); + close KEYS; + } + $keysfile->read(); + $key = Net::SSH::AuthorizedKey->parse($key); + my $add = 1; + foreach my $rkey($keysfile->keys()) { + if($key->fingerprint() eq $rkey->fingerprint()) { + $key = $rkey; + $add = 0; + last; + } + } + my $rrsync = join('/', $ENV{HOME}, "bin", "rrsync"); + if(! -f $rrsync) { + mkdir(join('/', $ENV{HOME}, "bin")); + system("gunzip -c /usr/share/doc/rsync/scripts/rrsync.gz > $rrsync"); + chmod 0755, $rrsync; + } + $key->options->{command} = "$rrsync -wo " . $self->eventpath; + if($add) { + $keys + } + $keys->save(); +} + +sub list { + my $self = shift; + + my $keysfile = Net::SSH::AuthorizedKeysFile->new(); + if(! -f $keysfile->path_locate()) { + return (); + } + $keysfile->read(); +} --- sreview-0.5.0.orig/lib/SReview/Web/Controller/Review.pm +++ sreview-0.5.0/lib/SReview/Web/Controller/Review.pm @@ -0,0 +1,172 @@ +package SReview::Web::Controller::Review; + +use Mojo::Base 'Mojolicious::Controller'; +use Mojo::Collection 'c'; + +use feature "switch"; + +use SReview::Talk; +use SReview::Access qw/admin_for/; + +sub view { + my $c = shift; + + my $id = $c->stash("id"); + my $talk; + $c->stash(adminspecial => 0); + eval { + if(defined($id)) { + $talk = SReview::Talk->new(talkid => $id); + } else { + $talk = SReview::Talk->by_nonce($c->stash('nonce')); + } + }; + if($@) { + $c->stash(error => $@); + $c->render(variant => 'error'); + return; + } + my $nonce = $talk->nonce; + my $variant; + if ($talk->state eq 'preview' || $talk->state eq 'broken') { + $variant = undef; + } elsif(admin_for($c, $talk)) { + $variant = undef; + $c->stash(adminspecial => 1); + } elsif($talk->state < 'preview') { + $variant = 'preparing'; + } elsif($talk->state < 'done') { + $variant = 'transcode'; + } else { + $variant = 'done'; + } + + my $vid_prefix = $c->srconfig->get('vid_prefix'); + $vid_prefix = '' unless defined($vid_prefix); + $c->stash(vid_prefix => $vid_prefix); + + $c->stash(talk => $talk); + $c->stash(stylesheets => ['/review.css']); + $c->render(variant => $variant); +} + +sub update { + my $c = shift; + my $id = $c->stash("id"); + my $talk; + + $c->stash(stylesheets => ['/review.css']); + if(defined($id)) { + $talk = SReview::Talk->new(talkid => $id); + } else { + eval { + $talk = SReview::Talk->by_nonce($c->stash('nonce')); + }; + if($@) { + $c->stash(error => $@); + $c->render(variant => 'error'); + return; + } + } + $c->stash(talk => $talk); + if(!admin_for($c, $talk) && $talk->state ne 'preview' && $talk->state ne 'broken') { + $c->stash(error => 'This talk is not currently available for review. Please try again later!'); + $c->render(variant => 'error'); + return; + } + $talk->add_correction(serial => 0); + if($c->param('serial') ne $talk->corrections->{serial}) { + $c->stash(error => 'This talk was updated (probably by someone else) since you last loaded it. Please reload the page, and try again.'); + $c->render(variant => 'error'); + return; + } + + if(defined($c->param("complete_reset")) && $c->param("complete_reset") == 1) { + $talk->reset_corrections(); + $talk->set_state("cutting"); + $c->render(variant => 'reset'); + return; + } + if(!defined($c->param("video_state"))) { + $c->stash(error => 'Invalid submission data; missing parameter video_state.'); + $c->render(variant => "error"); + return; + } + if($c->param("video_state") eq "ok") { + $talk->state_done("preview"); + $c->render(variant => 'done'); + return; + } + my $corrections = {}; + if(!defined($c->param("audio_channel"))) { + $c->stash(error => 'Invalid submission data; missing parameter audio_channel.'); + $c->render(variant => 'error'); + return; + } + if($c->param("audio_channel") ne "3") { + $talk->set_correction("audio_channel", $c->param("audio_channel")); + $corrections->{audio_channel} = $c->param("audio_channel"); + } else { + if($c->param("no_audio_options") eq "no_publish") { + $talk->set_state("broken"); + $talk->comment("The audio is broken; the talk should not be released."); + $talk->done_correcting; + $c->render(variant => 'other'); + return; + } + } + if(!defined($c->param("start_time"))) { + $c->stash(error => 'Invalid submission data; missing parameter start_time.'); + $c->render(variant => 'error'); + return; + } + if($c->param("start_time") ne "start_time_ok") { + $talk->add_correction("offset_start", $c->param("start_time_corrval")); + $corrections->{start} = $c->param("start_time_corrval"); + } + if(!defined($c->param("end_time"))) { + $c->stash(error => 'Invalid submission data; missing parameter end_time.'); + $c->render(variant => 'error'); + return; + } + if($c->param("end_time") ne "end_time_ok") { + $talk->add_correction("offset_end", $c->param("end_time_corrval")); + $corrections->{end} = $c->param("end_time_corrval"); + } + if(!defined($c->param("av_sync"))) { + $c->stash(error => 'Invalid submission data; missing parameter av_sync.'); + $c->render(variant => 'error'); + return; + } + if($c->param("av_sync") eq "av_not_ok_audio") { + $talk->add_correction("offset_audio", $c->param("av_seconds")); + $corrections->{audio_offset} = $c->param("av_seconds"); + } elsif($c->param("av_sync") eq "av_not_ok_video") { + $talk->add_correction("offset_audio", "-" . $c->param("av_seconds")); + $corrections->{audio_offset} = "-" . $c->param("av_seconds"); + } + if(defined($c->param("comment_text")) && length($c->param("comment_text")) > 0) { + $talk->comment($c->param("comment_text")); + $talk->set_state("broken"); + $c->stash(other_msg => $c->param("comment_text")); + $talk->done_correcting; + $c->render(variant => "other"); + return; + } + $talk->done_correcting; + $talk->set_state("waiting_for_files"); + $talk->state_done("waiting_for_files"); + $c->stash(corrections => $corrections); + $c->render(variant => 'newreview'); +} + +sub data { + my $c = shift; + my $talk = SReview::Talk->by_nonce($c->stash('nonce')); + + my $data = $talk->corrected_times; + + $c->render(json => $data); +} + +1; --- sreview-0.5.0.orig/lib/SReview/Web/Controller/Talk.pm +++ sreview-0.5.0/lib/SReview/Web/Controller/Talk.pm @@ -0,0 +1,49 @@ +package SReview::Web::Controller::Talk; + +use Mojo::Base 'Mojolicious::Controller'; +use SReview::API::Helpers qw/db_query/; +use Mojo::Util; + +sub create { + my $c = shift; + + if(!$c->auth_scope("api/talk/rw")) { + $c->res->code(403); + $c->render('Unauthorized'); + return 0; + } + + my $slug = $c->stash("slug"); + if(!defined($slug)) { + $slug = slugify($c->stash("title")); + } + + $c->render(json => db_query($c->dbh, "INSERT INTO talks(room, slug, starttime, endtime, title, event, upstreamid, subtitle, track, description) VALUES(?,?,?,?,?,?,?,?,?,?) RETURNING json_build_object('id', id)", $c->stash("room"), $slug, $c->stash("starttime"), $c->stash("endtime"), $c->stash("title"), $c->stash("event"), $c->stash("upstreamid"), $c->stash("subtitle"), $c->stash("track"), $c->stash("description"))); +} + +sub by_title { + my $c = shift; + $c->render(json => db_query($c->dbh, "SELECT row_to_json(talks.*) FROM talks WHERE title = ? AND event = ?", $c->stash("title"), $c->stash("event"))); +} + +sub by_id { + my $c = shift; + $c->render(json => db_query($c->dbh, "SELECT row_to_json(talks.*) FROM talks WHERE id = ? AND event = ?", $c->stash("id"), $c->stash("event"))); +} + +sub by_nonce { + my $c = shift; + $c->render(json => db_query($c->dbh, "SELECT row_to_json(talks.*) FROM talks WHERE nonce = ? AND event = ?", $c->stash("nonce"), $c->stash("event"))); +} + +sub list { + my $c = shift; + $c->render(json => db_query($c->dbh, "SELECT row_to_json(talks.*) FROM talks WHERE event = ?", $c->stash("event"))); +} + +sub delete { + my $c = shift; + $c->render(json => db_query($c->dbh, "DELETE FROM talks WHERE id = ? AND event = ?", $c->stash("id"), $c->stash("event"))); +} + +1; --- sreview-0.5.0.orig/lib/SReview/Web/Controller/Volunteer.pm +++ sreview-0.5.0/lib/SReview/Web/Controller/Volunteer.pm @@ -0,0 +1,31 @@ +package SReview::Web::Controller::Volunteer; + +use Mojo::Base 'Mojolicious::Controller'; + +sub list { + my $c = shift; + my @talks; + $c->dbh->begin_work; + my $already = $c->dbh->prepare("SELECT nonce, title, id, state FROM talks WHERE reviewer = ? AND state <= 'preview'"); + my $new = $c->dbh->prepare("SELECT nonce, title, id, state FROM talks WHERE reviewer IS NULL AND state = 'preview'::talkstate LIMIT ? FOR UPDATE"); + my $claim = $c->dbh->prepare("UPDATE talks SET reviewer = ? WHERE id = ?"); + $already->execute($c->session->{id}); + my $count = $already->rows; + if($count < 5) { + $new->execute(5 - $count); + } + for(my $i = 0; $i < $count; $i++) { + my $row = [ $already->fetchrow_array ]; + push @talks, $row; + } + for(my $i = 0; $i < $new->rows; $i++) { + my $row = [ $new->fetchrow_array ]; + $claim->execute($c->session->{id}, $row->[2]); + push @talks, $row; + } + $c->stash(talks => \@talks); + $c->stash(layout => 'admin'); + $c->dbh->commit; +} + +1; --- sreview-0.5.0.orig/scripts/notify/poke_track_managers +++ sreview-0.5.0/scripts/notify/poke_track_managers @@ -0,0 +1,99 @@ +#!/usr/bin/perl + +# SReview, a web-based video review and transcoding system +# Copyright (c) 2016-2017, Wouter Verhelst +# +# SReview is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero 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 +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with this program. If not, see +# . + +use strict; +use warnings; + +use Email::Sender::Simple qw(sendmail); +use Email::Simple; +use Email::MIME::Encodings; + +use DBI; + +our $config; + +require './config.pl'; + +my $dbh = DBI->connect($config->{dbistring}, '', '') or die "Cannot connect to database!"; + +my $recps = $dbh->prepare("SELECT title,speakeremail(talks.id) as speakers,nonce,name,email FROM talks JOIN tracks ON talks.track = tracks.id WHERE state='preview' ORDER BY email"); + +$recps->execute(); +my $trackmgr_email; +my $trackmgr_name; +my @talks; + +sub send_email { + my $talkdescs = join("\n",@talks); + my $body = < $body); + my $email = Email::Simple->create(header => + [ + From => '', + To => $trackmgr_email, + Subject => "Video review: please help keep the momentum going!", + "Reply-To" => '', + "Content-Transfer-Encoding" => "Quoted-Printable", + ], + body => $body + ); + + sendmail($email); +} + +while(my $row=$recps->fetchrow_hashref) { + if(defined($trackmgr_email)) { + if($row->{email} ne $trackmgr_email) { + send_email; + @talks = (); + } + } + $trackmgr_name = $row->{name}; + $trackmgr_email = $row->{email}; + my $speakers = defined($row->{speakers}) ? $row->{speakers} : "(not known)"; + $speakers =~ s/[^[:ascii:]]//g; + my $title = $row->{title}; + $title =~ s/[^[:ascii:]]//g; + push @talks, $title . ":\n\tspeakers:" . $speakers + . "\n\thttps://review.video.fosdem.org/review/" . $row->{nonce} . "\n"; +} + --- sreview-0.5.0.orig/scripts/sreview-encode +++ sreview-0.5.0/scripts/sreview-encode @@ -0,0 +1,95 @@ +#!/usr/bin/perl -w + +=head1 NAME + +sreview-encode - use the SReview library to transcode a file from one format to another + +=head1 SYNOPSIS + +sreview-encode --input input.mkv --output output.webm --profile webm --multipass + +=cut + +use strict; +use warnings; + +use SReview::Video; +use SReview::Video::ProfileFactory; +use SReview::Videopipe; +use File::Basename qw/dirname basename/; +use Getopt::Long; +use Data::Dumper; + +my $inputname; +my $outputname; +my $acopy = 0; +my $vcopy = 0; +my $multipass = 0; +my $profilename; + +=head1 OPTIONS + +=head2 B<--input>=FILE + +Use C as the input file to read video data from. Passed unmodified +to L's C<-i> parameter. No default; required. + +=head2 B<--output>=FILE + +Use C as the output file to write the transcoded video to. Passed +unmodified to L as the output file name. Default: the base +name (with whatever comes after the final dot, if anything, removed) of +the input file, supplemented with the default extension for the used +profile. + +=head2 B<--acopy> + +Enable the C option to the L being used, +which tells ffmpeg that it should not transcode the audio but just copy +it from one container to another, unmodified. + +=head2 B<--vcopy> + +Enable the C option to the L being used, +which has the same effect to video that C<--acopy> has to audio. + +=head2 B<--multipass> + +Enable a 2-pass transcode. + +=head2 B<--profile> + +Select the profile to be used. + +=cut + +GetOptions("input=s" => \$inputname, + "output=s" => \$outputname, + "acopy" => \$acopy, + "vcopy" => \$vcopy, + "multipass" => \$multipass, + "profile=s" => \$profilename) +or die "Error in command line arguments"; + +die "need input filename!" unless defined($inputname); +die "need profile name!" unless defined($profilename); + +sub progress { + my $perc = shift; + print "$outputname: $perc\%\r"; +} + +my $input = SReview::Video->new(url => $inputname); +my $profile = SReview::Video::ProfileFactory->create($profilename, $input); +if(!defined($outputname)) { + $outputname = $inputname; + $outputname =~ s/\.[^\.]*$//g; + $outputname .= "." . $profile->exten; +} +my $output = SReview::Video->new(url => $outputname, reference => $profile); + +$| = 1; + +SReview::Videopipe->new(inputs => [$input], output => $output, acopy => $acopy, vcopy => $vcopy, multipass => $multipass, progress => \&progress)->run(); + +print "\n"; --- sreview-0.5.0.orig/scripts/sreview-keys +++ sreview-0.5.0/scripts/sreview-keys @@ -0,0 +1,134 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use SReview::Model::Event; +use Net::SSH::AuthorizedKeysFile; +use Net::SSH::AuthorizedKey; +use Getopt::Long; +use Pod::Usage; + +use SReview::Config::Common; + +die 'Need $HOME to be set!' unless exists($ENV{HOME}); + +my $config = SReview::Config::Common::setup; + +my $eventname = $config->get('event'); +my $action = "add"; +my $oknodo = 0; +my $help = 0; +my $keyfile = undef; +my $bindir = $ENV{HOME} . "/bin"; + +GetOptions( + "event|e=s" => \$eventname, + "action|a=s" => \$action, + "help" => \$help, + "oknodo|o" => \$oknodo, + "file|f=s" => \$keyfile, +) or pod2usage("command line invalid"); + +=head1 NAME + +sreview-keys - manage keys in SReview's C file. + +=head1 SYNOPSIS + + sreview-keys --event="event name" --action="add" --file=./id_rsa.pub + sreview-keys -a "remove" --file=./id_rsa.pub + +=head1 DESCRIPTION + +sreview-keys is a simple tool to manage keys in an C +file so that when you run C to sync input data to the SReview +master server, the files are written to that event's own input directory +(and not elsewhere). + +The default event name is taken from the SReview configuration file (see +L, or it can be overridden with C<--event> (alias: +C<-e>). + +The default action is to add a key; to remove one, either edit the file, +or use the C<--action=remove> (alias: C<-a remove>) option. + +=cut + +if($help) { + pod2usage(0); +} + +if(!defined($keyfile)) { + pod2usage("key file not specified"); +} + +if($action ne "add" && $action ne "remove") { + print STDERR "Unknown action: $action\n"; + exit 1; +} + +my $event = SReview::Model::Event->new(config => $config, name => $eventname); + +my $akf = Net::SSH::AuthorizedKeysFile->new(); +my $file = $config->get('authkeyfile'); +$akf->read($file); + +open KEY, "<", $keyfile; +my $mkey = ""; +while() { + chomp; + $mkey .= $_; +} +close KEY; + +$mkey = Net::SSH::AuthorizedKey->parse($mkey); + +my @newkeys = (); + +foreach my $key($akf->keys()) { + if($key->fingerprint() eq $mkey->fingerprint()) { + if($action eq "add") { + if(!$oknodo) { + print STDERR "The provided key already exists in the file! Please remove it first\n"; + exit 1; + } else { + print "Key already added, ignoring\n"; + exit 0; + } + } else { + # don't add to @mkeys + next; + } + } + push @newkeys, $key; +} + +if($action eq "add") { + if(! -x "$bindir/rrsync") { + print STDERR "E: please install rrsync as $bindir/rrsync, and make sure it's executable (hint: /usr/share/doc/rsync/scripts/rrsync.gz)"; + exit 1; + } + my $iglob = $config->get('inputglob'); + my @input = split('/', $iglob); + my @dirs = (); + foreach my $in(@input) { + if($in =~ /\*/) { + last; + } + push @dirs, $in; + } + $mkey->option("command", "$bindir/rrsync '" . join('/', @dirs, $event->inputdir) . "'", 1); + $mkey->option("no-agent-forwarding", 1, 1); + $mkey->option("no-port-forwarding", 1, 1); + $mkey->option("no-pty", 1, 1); + $mkey->option("no-user-rc", 1, 1); + $mkey->option("no-X11-forwarding", 1, 1); + push @newkeys, $mkey; +} + +if(!defined($file)) { + $file = $akf->path_locate; +} +$akf = Net::SSH::AuthorizedKeysFile->new(keys => \@newkeys, file => $file); +$akf->save(); --- sreview-0.5.0.orig/t/010-config.t +++ sreview-0.5.0/t/010-config.t @@ -0,0 +1,45 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Test::More tests => 9; +use File::Temp qw/tempfile/; +use_ok('SReview::Config'); + +my $val; +local $SIG{__WARN__} = sub { $val = shift; }; + +my $config = SReview::Config->new('config'); +ok($val =~ /^Warning: could not find configuration file config, falling back to defaults at t\/010-config\.t line \d+\.$/, 'loading nonexisting config produces a warning but succeeds'); +isa_ok($config, 'SReview::Config'); + +$val = ''; +$config = SReview::Config->new('t/test.cf'); +ok(length($val) == 0, 'loading an existing config file succeeds and prints no warning'); + +$config->define('test', 'testingk', 1); +my $rv = $config->dump(); +my @expect = ("# testingk", "#\$test = 1;", "# Do not remove this, perl needs it", "1;"); +my $ok = 1; +foreach my $line(split /\n/, $rv) { + next unless length($line); + my $expline = shift @expect; + if($expline ne $line) { + $ok = 0; + } +} +ok($ok, "Config dump output is as expected"); +ok($config->describe('test') eq 'testingk', "Description of configuration value is as expected"); +my ($f, $filename) = tempfile('configtest-XXXXXXXX', UNLINK => 1); +print $f '{'; +eval { + my $config = SReview::Config->new($filename); +}; +ok(defined($@), "Trying to parse a syntactically invalid perl script produces an exception"); +eval { + my $val = $config->get('foo'); +}; +ok(defined($@), "Trying to read a config variable that does not exist produces an exception"); +$val = $config->get('test'); +ok($val == 1, "Reading data that does not exist yet produces the default"); --- sreview-0.5.0.orig/t/020-convert.t +++ sreview-0.5.0/t/020-convert.t @@ -0,0 +1,23 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Test::More tests => 10; +use_ok('SReview::Video'); +use_ok('SReview::Videopipe'); + +my $input = SReview::Video->new(url => 't/testvids/bbb.mp4'); +isa_ok($input, 'SReview::Video'); +my $output = SReview::Video->new(url => 't/testvids/1sec.webm', video_codec => 'libvpx-vp9', audio_codec => 'libopus', duration => 1, audio_bitrate => '128k'); +isa_ok($output, 'SReview::Video'); +my $pipe = SReview::Videopipe->new(inputs => [$input], output => $output, vcopy => 0, acopy => 0); +isa_ok($pipe, 'SReview::Videopipe'); +$pipe->run; +ok(-f $output->url, 'The output file exists'); +my $check = SReview::Video->new(url => $output->url); +isa_ok($check, 'SReview::Video'); +ok($check->video_size eq $input->video_size, 'The video was generated with the correct output size'); +ok($check->video_codec eq 'vp9', 'The video is encoded using VP9'); +ok($check->audio_codec eq 'opus', 'The audio is encoded using Opus'); +unlink($output->url); --- sreview-0.5.0.orig/t/030-copy.t +++ sreview-0.5.0/t/030-copy.t @@ -0,0 +1,24 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Test::More tests => 10; +use_ok('SReview::Video'); +use_ok('SReview::Videopipe'); + +my $input = SReview::Video->new(url => 't/testvids/bbb.mp4'); +isa_ok($input, 'SReview::Video'); +my $output = SReview::Video->new(url => 't/testvids/from10.mp4', fragment_start => 10); +isa_ok($output, 'SReview::Video'); +my $pipe = SReview::Videopipe->new(inputs => [$input], output => $output); +isa_ok($pipe, 'SReview::Videopipe'); +unlink($output->url); +$pipe->run; +ok(-f $output->url, 'The output file exists'); +my $check = SReview::Video->new(url => $output->url); +isa_ok($check, 'SReview::Video'); +ok($check->duration > ($input->duration - 10.1) && $check->duration < ($input->duration - 9.9), "The video has (approximately) the correct length"); +ok($check->video_codec eq $input->video_codec, 'The video has the same codec as the input video'); +ok($check->audio_codec eq $input->audio_codec, 'The audio has the same codec as the input video'); +unlink($output->url); --- sreview-0.5.0.orig/t/040-db.t +++ sreview-0.5.0/t/040-db.t @@ -0,0 +1,34 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Test::More tests => 7; + +use SReview::Db; +use SReview::Config; +use SReview::Config::Common; + +use DBI; + +SKIP: { + skip("Can't test database work unless the SREVIEWTEST_DB environment variable points to a database which we may clobber and recreate", 7) unless defined($ENV{SREVIEWTEST_DB}); + + my $warn; + local $SIG{__WARN__} = sub { $warn = shift }; + + my $config = SReview::Config::Common::setup; + + isa_ok($config, 'SReview::Config'); + + $config->set(dbistring => 'dbi:Pg:dbname=' . $ENV{SREVIEWTEST_DB}); + + ok(SReview::Db::init($config), "Initializing the database was successful"); + ok(SReview::Db::selfdestruct(code => 0, init => 0), "Clobbering the database works"); + ok(SReview::Db::init($config), "Re-initializing the database after clobbering it was successful"); + my $db = DBI->connect($config->get('dbistring'), '', '', {AutoCommit => 1}); + ok(defined($db), "connecting to the database was successful"); + my $q = $db->prepare("SELECT * FROM raw_files"); + ok(defined($q), "preparing a query succeeds"); + ok(defined($q->execute), "running a query succeeds, and the tables exist"); +} --- sreview-0.5.0.orig/t/050-probe.t +++ sreview-0.5.0/t/050-probe.t @@ -0,0 +1,20 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Test::More tests => 12; +use_ok('SReview::Video'); +use_ok('SReview::Videopipe'); + +my $vid = SReview::Video->new(url => 't/testvids/bbb.mp4'); +isa_ok($vid, 'SReview::Video'); +ok($vid->duration == 20.024000, 'video duration probed correctly'); +ok($vid->video_codec eq 'h264', 'video codec probed correctly'); +ok($vid->audio_codec eq 'aac', 'audio codec probed correctly'); +ok($vid->video_size eq '854x480', 'video resolution probed correctly'); +ok($vid->video_bitrate == 1116207, 'video bitrate probed correctly'); +ok($vid->audio_bitrate == 133431, 'audio bitrate probed correctly'); +ok($vid->audio_samplerate == 44100, 'audio samplerate probed correctly'); +ok($vid->video_framerate eq '24/1', 'video framerate probed correctly'); +ok($vid->pix_fmt eq 'yuv420p', 'video pixel format probed correctly'); --- sreview-0.5.0.orig/t/060-profiles.t +++ sreview-0.5.0/t/060-profiles.t @@ -0,0 +1,63 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Test::More tests => 20; +use_ok('SReview::Video'); +use_ok('SReview::Videopipe'); +use_ok('SReview::Video::ProfileFactory'); + +$ENV{SREVIEW_WDIR} = './t'; + +open CONFIG, '>t/config.pm'; +print CONFIG "\$extra_profiles={test => { parent => 'mp4' }};\n1;\n"; +close CONFIG; + +my $input = SReview::Video->new(url => 't/testvids/bbb.mp4'); +ok(defined($input), "Could create the input video"); +ok($input->video_codec eq "h264", "video codec of input file is what we expected"); + +my $profile = SReview::Video::ProfileFactory->create("vp9", $input); +ok(defined($profile), "Could create a VP9 video profile based on the input video"); +ok($profile->video_codec ne $input->video_codec, "video codec of profiled file is not the same as the input video codec"); + +my $output = SReview::Video->new(url => "t/testvids/foo.webm", reference => $profile); +ok(defined($output), "Could create an output video from the profile"); +ok($output->video_height == $input->video_height, "The VP9 video has the same height as the input video"); + +$profile = SReview::Video::ProfileFactory->create('vp8_lq', $input); +ok(defined($profile), "Could create a VP8 LQ profile based on the input video"); +unlink($output->url); +$output = SReview::Video->new(url => "t/testvids/foo.webm", reference => $profile); +ok(defined($output), "Could create an output video from the LQ profile"); +ok($output->video_height < $input->video_height, "The LQ profile creates smaller videos"); +ok($output->video_codec eq "vp8", "A VP8 video has the correct video codec"); + +my $pipe = SReview::Videopipe->new(inputs => [$input], output => $output); +ok(defined($pipe), "We can create a video pipe from a profiled output file"); +$pipe->run(); + +my $check = SReview::Video->new(url => $output->url); +ok(-f $output->url, "Creating a profiled video creates output"); +ok($check->video_height eq $output->video_height, "Creating a scaled video produces smaller output") or diag($check->video_height, " is not the same as ", $output->video_height); + +my $copypr = SReview::Video::ProfileFactory->create("copy", $input); +isa_ok($copypr, 'SReview::Video::Profile::Base'); + +unlink($output->url); + +$output = SReview::Video->new(url => 't/testvids/foo.mp4', reference => $copypr); + +SReview::Videopipe->new(inputs => [$input], output => $output)->run(); + +ok(-f $output->url, "copying data by profile creates output"); + +eval { my $val = SReview::Video::ProfileFactory->create("unknown", $input); }; +ok($@, "Creating a nonexisting profile dies"); + +my $testprof = SReview::Video::ProfileFactory->create("test", $input); +ok(defined($testprof), "Can create a profile from config"); + +unlink($output->url); +unlink('t/config.pm'); --- sreview-0.5.0.orig/t/070-reporting.t +++ sreview-0.5.0/t/070-reporting.t @@ -0,0 +1,46 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Test::More tests => 7; + +use_ok('SReview::Video'); +use_ok('SReview::Videopipe'); + +my $input = SReview::Video->new(url => 't/testvids/bbb.mp4'); +my $output = SReview::Video->new(url => 't/testvids/out.ts', video_codec => 'mpeg2video'); + +my $old_perc; +my $ok = 1; + +sub progress { + my $perc = shift; + + print "progress: $perc\n"; + if(defined($old_perc) && $perc < $old_perc) { + $ok = 0; + } + $old_perc = $perc; +} + +my $pipe = SReview::Videopipe->new(inputs => [$input], output => $output, progress => \&progress, vcopy => 0, acopy => 0); + +isa_ok($pipe, 'SReview::Videopipe'); + +$pipe->run; +ok($ok == 1, "progress information is strictly increasing"); +ok($old_perc == 100, "progress stops at 100%"); + +$old_perc = undef; + +unlink($output->url); +$output = SReview::Video->new(url => 't/testvids/out.webm', duration => 10, video_codec => 'vp8', audio_codec => 'libvorbis'); +$pipe = SReview::Videopipe->new(inputs => [$input], output => $output, progress => \&progress, vcopy => 0, acopy => 0, multipass => 1); +$pipe->run; + +ok($ok == 1, "progress information is strictly incresing when doing multipass"); +ok($old_perc == 100, "progress stops at 100% when doing multipass"); + +unlink($output->url); +unlink($output->url . "-multipass-0.log"); --- sreview-0.5.0.orig/t/080-runthrough.t +++ sreview-0.5.0/t/080-runthrough.t @@ -0,0 +1,94 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Test::More tests => 11; + +use Cwd 'abs_path'; + +$ENV{SREVIEW_WDIR} = abs_path('.'); + +use DBI; +use SReview::Video; +use SReview::Config::Common; +use File::Path qw/make_path remove_tree/; +use_ok("SReview::Files::Factory"); + +remove_tree("t/inputdir", "t/outputdir", "t/pubdir"); +sub run { + my @command = @_; + + print "running: '", join("' '", @command), "'\n"; + system(@command) == 0 or die "system @command failed: $?"; +} + +SKIP: { + skip("Can't test database work unless the SREVIEWTEST_DB environment variable points to a database which we may clobber and recreate", 10) unless defined($ENV{SREVIEWTEST_DB}); + + # Prepare an input directory + make_path('t/inputdir/room1/2017-11-10'); + symlink('../../../testvids/bbb.mp4', 't/inputdir/room1/2017-11-10/17:00:00.mp4'); + + # Prepare the configuration + my @outputopts; + if(exists($ENV{SREVIEWTEST_S3_CONFIG}) && exists($ENV{SREVIEWTEST_BUCKET})) { + $ENV{SREVIEW_ACCESSMETHODS}='{"input":"direct","intermediate":"S3","output":"direct"}'; + $ENV{SREVIEW_S3_ACCESS_CONFIG}=$ENV{SREVIEWTEST_S3_CONFIG}; + @outputopts = ("--set", "pubdir=" . $ENV{SREVIEWTEST_BUCKET}); + } else { + @outputopts = ("--set", "pubdir=" . abs_path("t/pubdir")); + } + run("perl", "-I./blib/lib", "blib/script/sreview-config", "--action", "update", "--set", "dbistring=dbi:Pg:dbname=" . $ENV{SREVIEWTEST_DB}, "--set", "inputglob=" . abs_path("t/inputdir") . "/*/*/*", "--set", "outputdir=" . abs_path('t/outputdir'), "--set", "preroll_template=" . abs_path("t/testvids/just-title.svg"), "--set", "postroll_template=" . abs_path("t/testvids/just-title.svg"), @outputopts); + + ok(-f 'config.pm', "running sreview-config with -a update creates a config.pm"); + + my $config = SReview::Config::Common::setup; + isa_ok($config, 'SReview::Config'); + + $config->set('dbistring' => 'dbi:Pg:dbname=' . $ENV{SREVIEWTEST_DB}); + + # Prepare the input database + my $dbh = DBI->connect($config->get('dbistring')); + $dbh->prepare("INSERT INTO rooms(id, name, altname) VALUES (1, 'room1', 'Room1')")->execute() or die $!; + $dbh->prepare("INSERT INTO events(id, name) VALUES(1, 'Test event')")->execute() or die $!; + $dbh->prepare("INSERT INTO talks(id, room, slug, starttime, endtime, title, event, upstreamid) VALUES(1, 1, 'test-talk', '2017-11-10 17:00:00', '2017-11-10 17:00:10', 'Test talk', 1, '1')")->execute() or die $!; + + # Detect input files + run("perl", "-I./blib/lib", "blib/script/sreview-detect"); + + my $st = $dbh->prepare("SELECT * FROM raw_talks"); + $st->execute(); + ok($st->rows == 1, "sreview-detect detects one file"); + + my $row = $st->fetchrow_hashref(); + + my $input = SReview::Video->new(url => abs_path("t/testvids/bbb.mp4")); + # perform cut + run("perl", "-I./blib/lib", "blib/script/sreview-cut", $row->{talkid}); + + my $coll = SReview::Files::Factory->create("intermediate", $config->get("pubdir")); + ok($coll->has_file("1/2017-11-10/r/test-talk.mkv"), "The file is created and added to the collection"); + my $file = $coll->get_file(relname => "1/2017-11-10/r/test-talk.mkv"); + my $check = SReview::Video->new(url => $file->filename); + my $length = $check->duration; + ok($length > 9.75 && $length < 10.25, "The generated cut video is of approximately the right length"); + ok($check->video_codec eq $input->video_codec, "The input video codec is the same as the pre-cut video codec"); + ok($check->audio_codec eq $input->audio_codec, "The input audio codec is the same as the pre-cut audio codec"); + + run("perl", "-I./blib/lib", "blib/script/sreview-previews", $row->{talkid}); + + $file = $coll->get_file(relname => "1/2017-11-10/r/test-talk.mp4"); + $check = SReview::Video->new(url => $file->filename); + ok(($length * 0.9 < $check->duration) && ($length * 1.1 > $check->duration), "The preview video is of approximately the right length"); + + # perform transcode + run("perl", "-I./blib/lib", "blib/script/sreview-transcode", $row->{talkid}); + my $final = SReview::Video->new(url => abs_path("t/outputdir/Test event/room1/2017-11-10/test-talk.webm")); + ok($final->video_codec eq "vp9", "The transcoded video has the right codec"); + ok($final->audio_codec eq "opus", "The transcoded audio has the right codec"); + + run("perl", "-I./blib/lib", "blib/script/sreview-upload", $row->{talkid}); +} + +unlink("config.pm"); --- sreview-0.5.0.orig/t/090-talk.t +++ sreview-0.5.0/t/090-talk.t @@ -0,0 +1,45 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Test::More tests => 10; +use SReview::Config::Common; +use Data::Dumper; +use Cwd; + +my $config = SReview::Config::Common::setup; + +SKIP: { + skip("Can't test database work unless the SREVIEWTEST_DB environment varialbe points to a database which we may clobber and recreate", 10) unless defined($ENV{SREVIEWTEST_DB}); + + $config->set(dbistring => 'dbi:Pg:dbname=' . $ENV{SREVIEWTEST_DB}); + $config->set('output_subdirs' => [ 'eventid', 'event', 'room', 'date', 'year' ]); + $config->set('pubdir' => '/srv/sreview/web/public/video'); + + use_ok('SReview::Talk'); + + my $talk = SReview::Talk->new(talkid => 1); + isa_ok($talk, 'SReview::Talk'); + + ok($talk->workdir eq "/srv/sreview/web/public/video/1/2017-11-10/r", 'The workdir resolves to the correct value'); + + ok($talk->finaldir eq "/srv/sreview/output/1/Test event/room1/2017-11-10/2017", 'The output directory resolves to the correct value'); + + ok($talk->slug eq 'test-talk', 'The talk slug resolves to the correct value'); + + is_deeply($talk->corrections, { offset_start => 0, length_adj => 0, offset_audio => 0, audio_channel => 0}, 'Corrections are set correctly'); + is_deeply($talk->video_fragments, [ + { talkid => -1, rawid => 1, raw_filename => cwd() . '/t/inputdir/room1/2017-11-10/17:00:00.mp4', fragment_start => 0, raw_length => 20.024, raw_length_corrected => 0 }, + { talkid => 1, rawid => 1, raw_filename => cwd() . '/t/inputdir/room1/2017-11-10/17:00:00.mp4', fragment_start => 0, raw_length => 20.024, raw_length_corrected => 10 }, + { talkid => -2, rawid => 1, raw_filename => cwd() . '/t/inputdir/room1/2017-11-10/17:00:00.mp4', fragment_start => 10, raw_length => 20.024, raw_length_corrected => 10.024 }], + 'Video fragments are found correctly'); + + $talk->add_correction(offset_start => 2); + $talk->done_correcting; + ok($talk->corrections->{offset_start} == 2, 'Corrections are accepted'); + + my $newtalk = SReview::Talk->new(talkid => 1); + ok($newtalk->corrections->{offset_start} == 2, 'Corrections are written to the database'); + ok($newtalk->corrections->{length_adj} == -2, 'Start offset changes length adjustment'); +} --- sreview-0.5.0.orig/t/095-files.t +++ sreview-0.5.0/t/095-files.t @@ -0,0 +1,50 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Test::More tests => 12; +use SReview::Config::Common; +use Data::Dumper; +use File::Copy; +use Mojo::JSON qw/decode_json/; + +my $config = SReview::Config::Common::setup; + +$config->set("outputdir", "t/testvids"); +$config->set("accessmethods", {input => "direct", output => "direct", intermediate => "direct"}); + +use_ok("SReview::Files::Factory"); + +my $coll = SReview::Files::Factory->create("output", "t/testvids"); + +isa_ok($coll, "SReview::Files::Collection::Base"); +isa_ok($coll, "SReview::Files::Collection::direct"); +my $children = $coll->children; +isa_ok($children, "ARRAY"); +my $child = $children->[0]; +isa_ok($child, "SReview::Files::Access::Base"); +isa_ok($child, "SReview::Files::Access::direct"); +ok(defined($child->filename), "child has a filename"); + +SKIP: { + skip("Can't test S3 work unless the s3_access_config configuration is valid", 5) unless (exists($ENV{SREVIEWTEST_BUCKET}) && exists($ENV{SREVIEWTEST_S3_CONFIG})); + + $config->set("s3_access_config", decode_json($ENV{SREVIEWTEST_S3_CONFIG})); + $config->set("accessmethods", {input => "S3", output => "S3", intermediate => "S3"}); + $config->set("outputdir", $ENV{SREVIEWTEST_BUCKET}); + $coll = SReview::Files::Factory->create("output", $ENV{SREVIEWTEST_BUCKET}); + + isa_ok($coll, "SReview::Files::Collection::Base"); + isa_ok($coll, "SReview::Files::Collection::S3"); + + my $new = $coll->add_file(relname => $child->relname); + ok($new->relname eq $child->relname, "creating a file with a relname from another bucket creates the same relname"); + copy($child->filename, $new->filename); + $new->store_file; + $children = $coll->children; + ok($coll->has_file($new->relname), "adding a file creates it in the bucket"); + $new->delete; + $coll = SReview::Files::Factory->create("output", $ENV{SREVIEWTEST_BUCKET}); + ok(!($coll->has_file($new->relname)), "deleting a file removes it"); +} --- sreview-0.5.0.orig/t/100-mojo.t +++ sreview-0.5.0/t/100-mojo.t @@ -0,0 +1,103 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; + +use Cwd 'abs_path'; + +BEGIN { + if(exists($ENV{SREVIEWTEST_DB})) { + open my $config, ">config.pm"; + print $config '$secret="foo";' . "\n"; + print $config '$dbistring=\'dbi:Pg:dbname=' . $ENV{SREVIEWTEST_DB} . '\';' . "\n"; + print $config '$outputdir="' . abs_path('t/outputdir') . '";' . "\n"; + print $config '$pubdir="' . abs_path('t/pubdir') . '";' . "\n"; + close $config; + } +} + +use Test::More tests => 31; +use Test::Mojo; +use Mojo::File qw/path/; +use SReview::Talk; +use SReview::Video; + +my $cfgname = path()->to_abs->child('config.pm'); + +SKIP: { + skip("Need a database to play with", 31) unless exists($ENV{SREVIEWTEST_DB}); + + my $script = path(__FILE__); + $script = $script->dirname->child('..')->child('web')->child('sreview-web')->to_abs; + symlink "../t", "web/t"; + chdir($script->dirname); + my $t = Test::Mojo->new($script); + + my $talk = SReview::Talk->new(talkid => 1); + + $t->get_ok('/')->status_is(200)->content_like(qr/SReview/); + $t->get_ok('/o')->status_is(302)->header_like(Location => qr/overview$/); + $t->get_ok('/admin')->status_is(302)->header_like(Location => qr/login$/); + my $talkurl = '/r/' . $talk->nonce; + $t->get_ok($talkurl)->status_is(200) + ->text_is("h1>small" => $talk->eventname . " videos") + ->text_like("h1" => "/\\s*" . $talk->title . "\\s*/"); + $t->get_ok("$talkurl/data")->status_is(200) + ->json_is("/end" => $talk->corrected_times->{end}) + ->json_is("/start" => $talk->corrected_times->{start}) + ->json_is("/end_iso" => $talk->corrected_times->{end_iso}) + ->json_is("/start_iso" => $talk->corrected_times->{start_iso}); + + my $video = SReview::Video->new(url => $talk->outname . ".mkv"); + + $talk->set_state("preview"); + $talk = SReview::Talk->new(talkid => 1); + + my $formdata = { + start_time => "start_time_ok", + end_time => "end_time_late", + end_time_corrval => "0.5", + av_sync => "av_ok", + serial => $talk->corrections->{serial}, + video_state => "not_ok", + audio_channel => $talk->corrections->{audio_channel}, + }; + $t->post_ok("$talkurl/update" => form => $formdata)->status_is(200); + + $talk->set_state("preview"); + $talk = SReview::Talk->new(talkid => 1); + + $t->get_ok("$talkurl/data")->status_is(200) + ->json_like("/end" => qr/2017-11-10 17:00:10.5\+\d{2}/); + + $formdata->{av_sync} = "av_not_ok_audio"; + $formdata->{av_seconds} = "1"; + $formdata->{end_time} = "end_time_ok"; + $formdata->{serial} = $talk->corrections->{serial}; + delete $formdata->{end_time_corrval}; + + $t->post_ok("$talkurl/update" => form => $formdata)->status_is(200); + + $talk->set_state("preview"); + + $talk = SReview::Talk->new(talkid => 1); + ok($talk->corrections->{offset_audio} == 1, "audio delay A/V sync value is set correctly"); + ok($talk->corrections->{serial} == $formdata->{serial} + 1, "updates affect serial"); + + $formdata->{av_sync} = "av_not_ok_video"; + $formdata->{serial} = $talk->corrections->{serial}; + + $t->post_ok("$talkurl/update" => form => $formdata)->status_is(200); + + $talk->set_state("preview"); + + $talk = SReview::Talk->new(talkid => 1); + ok($talk->corrections->{offset_audio} == 0, "video delay A/V sync value is set correctly"); + + chdir(".."); + unlink("web/t"); +}; + +unlink($cfgname); + +done_testing(); --- sreview-0.5.0.orig/web/public/review.css +++ sreview-0.5.0/web/public/review.css @@ -0,0 +1,118 @@ +body { + font-size: 16px; +} + +.eventname { + display: block; + margin-bottom: 10px; +} + +img { + max-width: 100%; + height: auto; + display: block; +} + +.container { + margin-bottom: 40px; +} + +h1 > small { + display: block; + margin-bottom:10px; +} + +#main_video, #video_starts_too_late, #av_delay, #talk_info, .help-text, #video_ends_too_early, #instructions, #video_state_information { + margin-top: 20px; +} + +#talk_info .dl-horizontal { + margin-bottom: 5px; +} + +.dl-horizontal dt { + width: auto; + padding-right: 5px; +} + +.dl-horizontal dd { + margin-left: 0; +} + +#video_starts_too_early, #video_ends_too_late { + margin-top: 20px; + margin-bottom: 20px; +} + +.btn-block + .alert { + margin-top: 15px; +} + +.hidden { + display: none; +} + +.alert_with_control { + padding:5px 15px; +} + +#video_state_information .alert + .alert-info { + padding: 20px; +} + +.audio_player { + width: 90%; + padding: 10px 0 20px 0; +} + +.jumbotron { + padding-top: 30px; + padding-bottom: 30px; +} + +.jumbotron h2 { + font-size: 35px; + margin-bottom: 40px; +} + +.jumbotron ul { + margin-bottom: 20px; +} + +.jumbotron ul > li { + font-size: 21px; + font-weight: 200; +} + +.restore_original .btn { + margin-top: 20px; +} + +.restore_original, .error_div, .video_has_problems, .other_brokennes, .info_row { + margin-top: 20px; +} + +.video_has_problems legend { + margin-bottom: 10px; +} + +#av_delay .form-inline { + width: 40%; +} + +#main_action { + margin: 30px 0; +} + +#no_audio h5 { + margin-top: 15px; +} + +.glyphicon-question-sign { + color: #999999; + margin-left: 3px; +} + +a .glyphicon-question-sign:hover { + color: #000000; +} --- sreview-0.5.0.orig/web/templates/review/update.html+done.ep +++ sreview-0.5.0/web/templates/review/update.html+done.ep @@ -0,0 +1,16 @@ +

+ <%= $talk->eventname . " videos " =%> + <%= $talk->title =%> +

+
+
+
+

+ + Thank you for sending your review +

+

You have told us the video looks good, so we'll process it for publication.

+

We will send you an email when the video is published.

+
+
+
--- sreview-0.5.0.orig/web/templates/review/update.html+error.ep +++ sreview-0.5.0/web/templates/review/update.html+error.ep @@ -0,0 +1,18 @@ +

+ <%= $talk->eventname . " videos " =%> + <%= $talk->title =%> +

+
+
+
+

+ + There was an error processing your request +

+

The following error occurred while trying to process your request:

+
<%= $error =%>
+

If you do not know what to do with the above error, please + contact the video team.

+
+
+
--- sreview-0.5.0.orig/web/templates/review/update.html+newreview.ep +++ sreview-0.5.0/web/templates/review/update.html+newreview.ep @@ -0,0 +1,32 @@ +

+ <%= $talk->eventname . " videos " =%> + <%= $talk->title =%> +

+
+
+
+

+ + Thank you for sending your review +

+

Once we have fixed the video, we will send you an email so that you can review the changes.

+

Summary of video problems

+

You have told us the video has some problems. Here is a summary of what needs fixing:

+
    + % if (exists($corrections->{start})) { +
  • The start time should be corrected with an offset of <%= $corrections->{start} %> seconds
  • + % } + % if (exists($corrections->{end})) { +
  • The end time should be corrected with an offset of <%= $corrections->{end} %> seconds
  • + % } + % if (exists($corrections->{audio_channel})) { +
  • The audio channel should be changed to channel <%= $corrections->{audio_channel} %>
  • + % } + % if (exists($corrections->{audio_offset})) { +
  • An A/V synchronisation value of <%= $corrections->{audio_offset} %> seconds should be applied
  • + % } +
+

Don't worry if the numbers don't make much sense: the video processing scripts will know what to do.

+
+
+
--- sreview-0.5.0.orig/web/templates/review/update.html+other.ep +++ sreview-0.5.0/web/templates/review/update.html+other.ep @@ -0,0 +1,20 @@ +

+ <%= $talk->eventname . " videos " =%> + <%= $talk->title =%> +

+
+
+
+

+ + Thank you for sending your review +

+

You have told us the video is broken in an unusual way. This is what you wrote:

+
+ "<%= $other_msg %>" +
+

Someone from the video team will look into the problem and send you an email.

+

Hopefully we can fix it.

+
+
+
--- sreview-0.5.0.orig/web/templates/review/update.html+reset.ep +++ sreview-0.5.0/web/templates/review/update.html+reset.ep @@ -0,0 +1,16 @@ +

+ <%= $talk->eventname . " videos " =%> + <%= $talk->title =%> +

+
+
+
+

+ + Thank you for reviewing this video! +

+

You have asked us to restore the original video. +

Once it's done, We will send you an email.

+
+
+
--- sreview-0.5.0.orig/web/templates/review/view.html+done.ep +++ sreview-0.5.0/web/templates/review/view.html+done.ep @@ -0,0 +1,25 @@ +

+ <%= $talk->eventname . " videos" =%> + <%= $talk->title =%> +

+
+
+
+
Speakers:
+
<%= $talk->speakers =%>
+
Date:
+
<%= $talk->readable_date =%>
+
Room:
+
<%= $talk->room =%>
+
+
+
+ +
+
+ --- sreview-0.5.0.orig/web/templates/review/view.html+error.ep +++ sreview-0.5.0/web/templates/review/view.html+error.ep @@ -0,0 +1,8 @@ +

Error

+
+
+ +
+
--- sreview-0.5.0.orig/web/templates/review/view.html+preparing.ep +++ sreview-0.5.0/web/templates/review/view.html+preparing.ep @@ -0,0 +1,25 @@ +

+ <%= $talk->eventname . " videos" =%> + <%= $talk->title =%> +

+
+
+
+
Speakers:
+
<%= $talk->speakers =%>
+
Date:
+
<%= $talk->readable_date =%>
+
Room:
+
<%= $talk->room =%>
+
+
+
+ +
+
+ +
+
--- sreview-0.5.0.orig/web/templates/review/view.html+transcode.ep +++ sreview-0.5.0/web/templates/review/view.html+transcode.ep @@ -0,0 +1,25 @@ +

+ <%= $talk->eventname . " videos" =%> + <%= $talk->title =%> +

+
+
+
+
Speakers:
+
<%= $talk->speakers =%>
+
Date:
+
<%= $talk->readable_date =%>
+
Room:
+
<%= $talk->room =%>
+
+
+
+ +
+
+ --- sreview-0.5.0.orig/web/templates/review/view.html.ep +++ sreview-0.5.0/web/templates/review/view.html.ep @@ -0,0 +1,514 @@ +% if (my $cmessage = flash 'completion_message') { +
+ × + <%= $cmessage =%> +
+% } +% if (my $emessage = flash 'error_message') { +
+ × + <%= $emessage =%> +
+% } +% if ( $adminspecial ) { +
+ × +

Note: this talk is currently in the state <%= $talk->state %>, not in one of the preview or broken states. You can only see this review page because you are admin!

+

Please use caution when making changes.

+
+% } +

+ <%= $talk->eventname . " videos" =%> + <%= $talk->title =%> +

+
+
+
+
Speakers:
+
<%= $talk->speakers =%>
+
Date:
+
<%= $talk->readable_date =%>
+
Room:
+
<%= $talk->room =%>
+
+ Download video +
+
+
+
+

What to check when reviewing this video

+
    +
  1. Check the beginning of the video to make sure it starts at the right time.
  2. +
  3. Check the end of the video to make sure it finishes at the right time.
  4. +
  5. Check the sound to make sure it's ok.
  6. +
+

Then answer the How is this video question at the bottom of the page.

+

By the way, nothing you do here will result in data loss. In fact, you can restore the original video at any time.

+
+
+
+
+
+

Restore the original video

+

What you are about to do will remove all changes made to this video so far, and reset it to its initial state.

+

That is a pretty drastic thing to do, that's why we are asking you again.

+
+ + No, cancel this business +
+
+
+
+
+
+
+ +
+
+
+
+
+

How is this video?

+
+
+
+ +
+
+ +
+
+
+ Start time +
+ +
+
+ +
+
+ +
+
+
+
+ +
+

Find the correct start time, pause the video and click the big green button.

+ + +
+
+
+
+
+ +
+

Find the correct start time, pause the video and click the big green button.

+ + +
+
+ +
+
+
+
+
+
+ End time +
+ +
+
+ +
+
+ +
+
+
+
+ +
+

Find the correct end time, pause the video and click the big green button.

+ + +
+
+ +
+
+
+
+
+
+
+ +
+

Find the correct end time, pause the video and click the big green button.

+ + +
+
+
+
+ Sound +
+
+

Listen to a preview of all available sound channels, and select the best one.

+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ Audio/video synchronisation +
+
+

Here you can report if audio and video are out of sync, as in a badly dubbed movie.

+
+
+
+ +
+
+ +
+
+ +
+
+
+

Can you tell us the time difference between the audio and the video?

+
+
+
+ +
seconds
+
+
+
+
+
+
+
+ Other brokenness +
+
+ + +

Note: if you enter anything in the + field above, the video will be marked as + broken, and will not be released + until an administrator has looked at + it (or you remove the text again). If that's not + what you want, please leave the field empty.

+

For feedback that is not critical for the release of + your talk, you may contact us through email or IRC

+
+
+
+ + + + + +
+
+
+