diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42a6039..bf01945 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,29 +40,6 @@ jobs: - name: Build run: npx gulp build - - name: Check for unstaged changes - run: | - if ! git diff --exit-code -- video2commons/frontend/static/*.min.js video2commons/frontend/templates/*.min.html; then - echo "Built files differ from committed files." - git diff --stat -- video2commons/frontend/static/*.min.js video2commons/frontend/templates/*.min.html - echo "NEEDS_COMMIT=true" >> $GITHUB_ENV - else - echo "Built files match committed files." - echo "NEEDS_COMMIT=false" >> $GITHUB_ENV - fi - - - name: Commit and push changes - if: env.NEEDS_COMMIT == 'true' - run: | - git checkout HEAD -- video2commons/frontend/static/ssu video2commons/frontend/static/uploads - git fetch origin $GITHUB_HEAD_REF - git checkout $GITHUB_HEAD_REF - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add video2commons/frontend/static/*.min.js video2commons/frontend/templates/*.min.html - git commit -m "Update built files from CI" - git push origin $GITHUB_HEAD_REF - biome: runs-on: ubuntu-latest diff --git a/.github/workflows/deploy-cloudvps-encoders.yml b/.github/workflows/deploy-cloudvps-encoders.yml new file mode 100644 index 0000000..cf01e53 --- /dev/null +++ b/.github/workflows/deploy-cloudvps-encoders.yml @@ -0,0 +1,53 @@ +name: Deploy Encoders (CloudVPS) + +on: + workflow_dispatch: + + # Uncomment to allow the workflow to run automatically when changes are + # merged into master (only for files that affect this deployment). + # + # push: + # branches: [master] + # paths: + # - 'puppet/**' + # - 'pyproject.toml' + # - 'utils/deploy-cloudvps-encoders.sh' + # - 'uv.lock' + # - 'video2commons/backend/**' + # - 'video2commons/config.py' + # - 'video2commons/exceptions.py' + # - 'video2commons/shared/**' + +jobs: + deploy-cloudvps-encoders: + name: Deploy Encoders (CloudVPS) + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + cat >> ~/.ssh/config << 'EOF' + Host * + IdentityFile ~/.ssh/id_ed25519 + IdentitiesOnly yes + StrictHostKeyChecking no + + Host *.wikimedia.cloud + ProxyJump login.toolforge.org:22 + EOF + chmod 600 ~/.ssh/config + mkdir -p ~/.ssh/sockets + + - name: Deploy + run: ./utils/deploy-cloudvps-encoders.sh + env: + V2C_USERNAME: ${{ secrets.V2C_USERNAME }} + V2C_REDIS_PW: ${{ secrets.V2C_REDIS_PW }} + V2C_CONSUMER_SECRET: ${{ secrets.V2C_CONSUMER_SECRET }} + V2C_CONSUMER_KEY: ${{ secrets.V2C_CONSUMER_KEY }} diff --git a/.github/workflows/deploy-toolforge-video2commons-socketio.yml b/.github/workflows/deploy-toolforge-video2commons-socketio.yml new file mode 100644 index 0000000..d40df55 --- /dev/null +++ b/.github/workflows/deploy-toolforge-video2commons-socketio.yml @@ -0,0 +1,47 @@ +name: Deploy Socket.IO (Toolforge) + +on: + workflow_dispatch: + + # Uncomment to allow the workflow to run automatically when changes are + # merged into master (only for files that affect this deployment). + # + # push: + # branches: [master] + # paths: + # - 'package-lock.json' + # - 'package.json' + # - 'utils/deploy-toolforge-socketio.sh' + # - 'video2commons-socketio/**' + +jobs: + deploy-toolforge-video2commons-socketio: + name: Deploy Socket.IO (Toolforge) + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo '${{ secrets.SSH_PRIVATE_KEY }}' > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + cat >> ~/.ssh/config << 'EOF' + Host * + IdentityFile ~/.ssh/id_ed25519 + IdentitiesOnly yes + StrictHostKeyChecking no + ControlMaster auto + ControlPath ~/.ssh/sockets/%r@%h-%p + ControlPersist 60 + EOF + chmod 600 ~/.ssh/config + mkdir -p ~/.ssh/sockets + + - name: Deploy + run: ./utils/deploy-toolforge-socketio.sh + env: + V2C_SERVICE_NAME: video2commons-socketio + V2C_USERNAME: ${{ secrets.V2C_USERNAME }} diff --git a/.github/workflows/deploy-toolforge-video2commons-test.yml b/.github/workflows/deploy-toolforge-video2commons-test.yml new file mode 100644 index 0000000..2e33def --- /dev/null +++ b/.github/workflows/deploy-toolforge-video2commons-test.yml @@ -0,0 +1,66 @@ +name: Deploy Test Frontend (Toolforge) + +on: + workflow_dispatch: + + push: + branches: [master] + paths: + - 'Gulpfile.mjs' + - 'package-lock.json' + - 'package.json' + - 'pyproject.toml' + - 'utils/deploy-toolforge-frontend.sh' + - 'uv.lock' + - 'video2commons/config.py' + - 'video2commons/exceptions.py' + - 'video2commons/frontend/**' + - 'video2commons/shared/**' + +jobs: + deploy-toolforge-video2commons-test: + name: Deploy Test Frontend (Toolforge) + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Remove broken symlinks + run: | + rm -f video2commons/frontend/static/ssu + rm -f video2commons/frontend/static/uploads + + - name: Build + run: | + npm ci + npx gulp build + + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo '${{ secrets.SSH_PRIVATE_KEY }}' > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + cat >> ~/.ssh/config << 'EOF' + Host * + IdentityFile ~/.ssh/id_ed25519 + IdentitiesOnly yes + StrictHostKeyChecking no + ControlMaster auto + ControlPath ~/.ssh/sockets/%r@%h-%p + ControlPersist 60 + EOF + chmod 600 ~/.ssh/config + mkdir -p ~/.ssh/sockets + + - name: Deploy + run: ./utils/deploy-toolforge-frontend.sh + env: + V2C_SERVICE_NAME: video2commons-test + V2C_USERNAME: ${{ secrets.V2C_USERNAME }} diff --git a/.github/workflows/deploy-toolforge-video2commons.yml b/.github/workflows/deploy-toolforge-video2commons.yml new file mode 100644 index 0000000..9584c3e --- /dev/null +++ b/.github/workflows/deploy-toolforge-video2commons.yml @@ -0,0 +1,69 @@ +name: Deploy Frontend (Toolforge) + +on: + workflow_dispatch: + + # Uncomment to allow the workflow to run automatically when changes are + # merged into master (only for files that affect this deployment). + # + # push: + # branches: [master] + # paths: + # - 'Gulpfile.mjs' + # - 'package-lock.json' + # - 'package.json' + # - 'pyproject.toml' + # - 'utils/deploy-toolforge-frontend.sh' + # - 'uv.lock' + # - 'video2commons/config.py' + # - 'video2commons/exceptions.py' + # - 'video2commons/frontend/**' + # - 'video2commons/shared/**' + +jobs: + deploy-toolforge-video2commons: + name: Deploy Frontend (Toolforge) + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Remove broken symlinks + run: | + rm -f video2commons/frontend/static/ssu + rm -f video2commons/frontend/static/uploads + + - name: Build + run: | + npm ci + npx gulp build + + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo '${{ secrets.SSH_PRIVATE_KEY }}' > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + cat >> ~/.ssh/config << 'EOF' + Host * + IdentityFile ~/.ssh/id_ed25519 + IdentitiesOnly yes + StrictHostKeyChecking no + ControlMaster auto + ControlPath ~/.ssh/sockets/%r@%h-%p + ControlPersist 60 + EOF + chmod 600 ~/.ssh/config + mkdir -p ~/.ssh/sockets + + - name: Deploy + run: ./utils/deploy-toolforge-frontend.sh + env: + V2C_SERVICE_NAME: video2commons + V2C_USERNAME: ${{ secrets.V2C_USERNAME }} diff --git a/.gitignore b/.gitignore index 2f6b4c7..f7b2229 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,5 @@ service.manifest uwsgi.log uwsgi.log.* www/js/ +video2commons/frontend/static/*.min.js +video2commons/frontend/templates/*.min.html diff --git a/puppet/backend.pp b/puppet/backend.pp index 7ae8bab..1c596f8 100644 --- a/puppet/backend.pp +++ b/puppet/backend.pp @@ -176,6 +176,7 @@ EnvironmentFile=-/etc/default/v2ccelery WorkingDirectory=/srv/v2c Restart=on-failure +TimeoutStopSec=infinity ExecStart=/bin/sh -c \'${CELERY_BIN} multi start $CELERYD_NODES \ -A $CELERY_APP --logfile=${CELERYD_LOG_FILE} \ --pidfile=${CELERYD_PID_FILE} $CELERYD_OPTS\' diff --git a/utils/deploy.sh b/utils/deploy-cloudvps-encoders.sh similarity index 91% rename from utils/deploy.sh rename to utils/deploy-cloudvps-encoders.sh index 9179746..d83eb37 100755 --- a/utils/deploy.sh +++ b/utils/deploy-cloudvps-encoders.sh @@ -58,6 +58,9 @@ fi EOF ) +worker_count=$(echo "$encoder_hosts" | wc -l) +success_count=0 + while read -r encoder_host; do echo "Applying puppet manifest to '$encoder_host' and restarting v2c service..." @@ -67,7 +70,12 @@ while read -r encoder_host; do echo "Failed to apply puppet manifest to '$encoder_host'" >&2 else echo "Puppet manifest applied to '$encoder_host'" + success_count=$((success_count + 1)) fi done <<< "$encoder_hosts" -echo "Done" +echo "Done. Updated ($success_count/$worker_count) workers" + +if [ "$success_count" -ne "$worker_count" ]; then + exit 1 +fi diff --git a/utils/deploy-toolforge-frontend.sh b/utils/deploy-toolforge-frontend.sh new file mode 100755 index 0000000..ad7856f --- /dev/null +++ b/utils/deploy-toolforge-frontend.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# Note: All ssh commands share the same connection for the lifetime of this +# script if run via the workflows. +# +# The follow ssh options are used by the workflow: +# ControlMaster auto +# ControlPath ~/.ssh/sockets/%r@%h-%p +# ControlPersist 60 + +bastion_host=login.toolforge.org + +if [ -z "$V2C_USERNAME" ]; then + echo "Error: V2C_USERNAME environment variable is not set" >&2 + exit 1 +elif [ -z "$V2C_SERVICE_NAME" ]; then + echo "Error: V2C_SERVICE_NAME environment variable is not set" >&2 + exit 1 +fi + +remote_repo_path="/data/project/$V2C_SERVICE_NAME" +script_dir="$(cd "$(dirname "$0")" && pwd)" +host_repo_root="$script_dir/.." +tmp_dir="/tmp/v2c-deploy-$V2C_SERVICE_NAME" + +echo "Updating video2commons frontend..." + +# Pull in the latest changes from the repository currently in master. +ssh "$V2C_USERNAME@$bastion_host" "become $V2C_SERVICE_NAME bash -c 'cd $remote_repo_path && git pull'" + +if [ $? -ne 0 ]; then + echo "Failed to pull most recent changes for v2c" >&2 + exit 1 +fi + +# Create a temp directory for temporarily storing the minified files. +ssh "$V2C_USERNAME@$bastion_host" "mkdir -p $tmp_dir" + +if [ $? -ne 0 ]; then + echo "Failed to create temporary directory at $tmp_dir" >&2 + exit 1 +fi + +# Upload the minified files to the new temp directory. +scp "$host_repo_root/video2commons/frontend/static/"*.min.js \ + "$host_repo_root/video2commons/frontend/templates/"*.min.html \ + "$V2C_USERNAME@$bastion_host:$tmp_dir/" + +if [ $? -ne 0 ]; then + echo "Failed to copy minified files to remote" >&2 + ssh "$V2C_USERNAME@$bastion_host" "rm -rf $tmp_dir" + exit 1 +fi + +# Copy the minified files to destination with correct ownership, then cleanup. +ssh "$V2C_USERNAME@$bastion_host" "become $V2C_SERVICE_NAME bash -c ' + cp $tmp_dir/*.min.js $remote_repo_path/video2commons/frontend/static/ + cp $tmp_dir/*.min.html $remote_repo_path/video2commons/frontend/templates/ +' && rm -rf $tmp_dir" + +if [ $? -ne 0 ]; then + echo "Failed to move minified files to destination" >&2 + exit 1 +fi + +# Restart the webservice so any Python changes are applied. +ssh "$V2C_USERNAME@$bastion_host" "become $V2C_SERVICE_NAME toolforge webservice python3.11 restart" + +# Cleanup the SSH control socket that we use to keep the connection alive +# across multiple ssh command executions. +ssh -O exit "$V2C_USERNAME@$bastion_host" 2>/dev/null || true + +echo "Done" diff --git a/utils/deploy-toolforge-socketio.sh b/utils/deploy-toolforge-socketio.sh new file mode 100755 index 0000000..d4edeb5 --- /dev/null +++ b/utils/deploy-toolforge-socketio.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Note: All ssh commands share the same connection for the lifetime of this +# script if run via the workflows. +# +# The follow ssh options are used by the workflow: +# ControlMaster auto +# ControlPath ~/.ssh/sockets/%r@%h-%p +# ControlPersist 60 + +bastion_host=login.toolforge.org + +if [ -z "$V2C_USERNAME" ]; then + echo "Error: V2C_USERNAME environment variable is not set" >&2 + exit 1 +elif [ -z "$V2C_SERVICE_NAME" ]; then + echo "Error: V2C_SERVICE_NAME environment variable is not set" >&2 + exit 1 +fi + +remote_repo_path="/data/project/$V2C_SERVICE_NAME" + +echo "Updating video2commons socket.io backend..." + +# Pull in the latest changes from the repository currently in master. +ssh "$V2C_USERNAME@$bastion_host" "become $V2C_SERVICE_NAME bash -c 'cd $remote_repo_path && git pull'" + +if [ $? -ne 0 ]; then + echo "Failed to pull most recent changes for v2c" >&2 + exit 1 +fi + +# Restart the webservice so any JavaScript changes are applied. +ssh "$V2C_USERNAME@$bastion_host" "become $V2C_SERVICE_NAME toolforge webservice --backend=kubernetes node18 restart" + +# Cleanup the SSH control socket that we use to keep the connection alive +# across multiple ssh command executions. +ssh -O exit "$V2C_USERNAME@$bastion_host" 2>/dev/null || true + +echo "Done" diff --git a/video2commons/frontend/static/templates.min.js b/video2commons/frontend/static/templates.min.js deleted file mode 100644 index c1e530c..0000000 --- a/video2commons/frontend/static/templates.min.js +++ /dev/null @@ -1 +0,0 @@ -(window.nunjucksPrecompiled=window.nunjucksPrecompiled||{})["addTask.html"]={root:function(e,o,a,l,s){var t=0,r=0,p="";try{s(null,(p+='')}catch(e){s(l.handleError(e,t,r))}}},(window.nunjucksPrecompiled=window.nunjucksPrecompiled||{})["confirmForm.html"]={root:function(e,o,a,l,s){var t=0,r=0,p="";try{s(null,(p+='

'+l.suppressValue((t=0,r=1032,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["confirmmsg"])),e.opts.autoescape)+"
")}catch(e){s(l.handleError(e,t,r))}}},(window.nunjucksPrecompiled=window.nunjucksPrecompiled||{})["playlistConfirmForm.html"]={root:function(e,o,a,l,s){var t=0,r=0,p="";try{p=(p=(p+='

    ',a=a.push();var c=l.memberLookup(l.contextOrFrameLookup(o,a,"task"),"selectedVideos");if(c)for(var i=(c=l.fromIterator(c)).length,n=0;n")+l.suppressValue(l.memberLookup(u,"filename"),e.opts.autoescape)+""}a=a.pop(),s(null,p=(p+='
')+l.suppressValue((t=0,r=677,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["confirmmsg"])),e.opts.autoescape)+"
")}catch(e){s(l.handleError(e,t,r))}}},(window.nunjucksPrecompiled=window.nunjucksPrecompiled||{})["playlistForm.html"]={root:function(e,o,a,l,s){var t=0,r=0,p="";try{p=(p=(p=(p=(p=(p+='

')+l.suppressValue(l.memberLookup(l.contextOrFrameLookup(o,a,"task"),"title"),e.opts.autoescape)+'

",a=a.push();var c=l.memberLookup(l.contextOrFrameLookup(o,a,"task"),"videos");if(c)for(var i=(c=l.fromIterator(c)).length,n=0;n'}i||(p=(p+='"),a=a.pop(),s(null,p+="
')+l.suppressValue((t=0,r=456,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["filename"])),e.opts.autoescape)+"")+l.suppressValue((t=0,r=484,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["transcoding"])),e.opts.autoescape)+'')+l.suppressValue((t=0,r=535,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["actions"])),e.opts.autoescape)+"
')+l.suppressValue(l.memberLookup(u,"filename"),e.opts.autoescape)+'')+l.suppressValue(l.memberLookup(u,"format"),e.opts.autoescape)+'
')+l.suppressValue((t=0,r=1231,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["noVideos"])),e.opts.autoescape)+"
")}catch(e){s(l.handleError(e,t,r))}}},(window.nunjucksPrecompiled=window.nunjucksPrecompiled||{})["sourceForm.html"]={root:function(e,o,a,l,s){var t=0,r=0,p="";try{s(null,(p+='
')+l.suppressValue((t=0,r=325,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["uploadFile"])),e.opts.autoescape)+'
'+l.suppressValue((t=0,r=1336,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["note"])),e.opts.autoescape)+""+l.suppressValue((t=0,r=1463,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["importantnote"])),e.opts.autoescape)+" "+l.suppressValue(e.getFilter("process_link").call(o,(t=0,r=1492,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["noteslicensing"]))),e.opts.autoescape)+"
")}catch(e){s(l.handleError(e,t,r))}}},(window.nunjucksPrecompiled=window.nunjucksPrecompiled||{})["targetForm.html"]={root:function(e,o,a,l,s){var t=0,r=0,p="";try{p=(p=(p=(p=(p+='

.

";p=l.memberLookup(l.contextOrFrameLookup(o,a,"source"),"audio")&&!l.memberLookup(l.contextOrFrameLookup(o,a,"source"),"video")?(p+=' ')+l.suppressValue(e.getFilter("replace").call(o,(t=0,r=1093,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["datecategory-help"])),"$1",'"Audio files of YYYY"'),e.opts.autoescape)+" ":(p+=' ')+l.suppressValue(e.getFilter("replace").call(o,(t=0,r=1206,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["datecategory-help"])),"$1",'"Videos of YYYY", "Videos taken on YYYY-MM-DD"'),e.opts.autoescape)+" ",s(null,p=(p+='
')+l.suppressValue((t=0,r=1585,l.callWrap(l.contextOrFrameLookup(o,a,"_"),"_",o,["extensionmsg"])),e.opts.autoescape)+"
")}catch(e){s(l.handleError(e,t,r))}}}; \ No newline at end of file diff --git a/video2commons/frontend/static/video2commons.min.js b/video2commons/frontend/static/video2commons.min.js deleted file mode 100644 index 5208810..0000000 --- a/video2commons/frontend/static/video2commons.min.js +++ /dev/null @@ -1,5 +0,0 @@ -(s=>{var n,r,l,t,i,o=window.config,d=window.i18n,c='File:Ajax-loader.gif',e="rtl"===d["@dir"],u={abortbutton:``,removebutton:``,restartbutton:``,loading:`
${c}  ${nunjucks.lib.escape(d.loading)}
`,errorDisconnect:`
${nunjucks.lib.escape(d.errorDisconnect)}
`,yourTasks:`

${nunjucks.lib.escape(d.yourTasks)}

`,workers:`

${nunjucks.lib.escape(d.workers)}

`,capacity:e?`
... ${d.capacity}
`:`
${d.capacity} ...
`,utilization:e?`
... ${d.utilization}
`:`
${d.utilization} ...
`,pending:e?`
... ${d.pending}
`:`
${d.pending} ...
`,addTask:``,requestServerSide:`${nunjucks.lib.escape(d.createServerSide)}`,progressbar:'
',prevbutton:` `+nunjucks.lib.escape(d.back),nextbutton:nunjucks.lib.escape(d.next)+` `,confirmbutton:nunjucks.lib.escape(d.confirm)},g="",p=(new nunjucks.Environment).addGlobal("config",o).addGlobal("_",e=>d[e]).addFilter("process_link",e=>{for(var a,t=/\{\{#a\}\}(.*?)\{\{\/a\}\}/g,i=0,o="";null!==(a=t.exec(e));)o=(o+=nunjucks.lib.escape(e.substring(i,a.index)))+(e=>{if("#"===e[0]){var a=e.indexOf("|");if(a<0){if(/^[a-z0-9-]+$/i.test(e.slice(1)))return``}else if(/^[a-z0-9-]+$/i.test(e.substring(1,a)))return`${nunjucks.lib.escape(e.slice(a+1))}`}return`${nunjucks.lib.escape(e)}`})(a[1]),i=t.lastIndex;return o+=nunjucks.lib.escape(e.slice(i)),new nunjucks.runtime.SafeString(o)});function b(e,a){var t,i,o,n;return a?!e.video&&e.audio?(t=(e=a).match(/^Audio files of (\d{4})$/))?(t=parseInt(t[1],10),(new Date).getFullYear()({url:n.find("#url").val(),video:n.find("#video").is(":checked"),audio:n.find("#audio").is(":checked"),subtitles:n.find("#subtitles").is(":checked")}),getTargetData:()=>({filename:n.find("#filename").val().trim(),format:n.find("#format").val(),filedesc:n.find("#filedesc").val(),dateCategory:n.find("#dateCategory").val().trim(),languageCategory:n.find("#languageCategory").val()||""}),getPlaylistData:()=>{let a=[];return n.find(".video-select:checked").each(function(){var e=parseInt(s(this).val(),10),e=r.videos[e];a.push(e)}),a}},v={startTask:e=>V.apiPost("task/run",e),updateFormats:(e,a,t)=>0{var e;return a?a===r.url&&r.initialFilenameValidated?s.when():(e=r.uploadedFile[a])?(r.url=a,V.askAPI("makedesc",{filename:e.name||""},["extractor","filedesc","filename"])):V.askAPI("validateurl",{url:a},["entity_url"]).then(e=>e.entity_url?s.Deferred().reject("This video has already been uploaded: "+e.entity_url).promise():V.askAPI("extracturl",{url:a},["type","id","title","url","date","extractor","filedesc","filename","videos","queue"]).then(()=>{r.initialFilenameValidated=!0})).then(()=>{"playlist"===r.type?r.videos.forEach(e=>{e.format=r.format,e.dateCategory=y(e)}):r.dateCategory=y(r)}):s.Deferred().reject("URL cannot be empty!").promise()},updateFilename:(e,a=null)=>(null==a&&(a=r),e?e===a.filename&&a.initialFilenameValidated?s.when():V.askAPI("validatefilename",{filename:e},[],a).then(()=>V.askAPI("validatefilenameunique",{filename:e},["filename"],a)).then(()=>{a.initialFilenameValidated=!0}):s.Deferred().reject("Filename cannot be empty!").promise()),updateFiledesc:(e,a=null)=>(null==a&&(a=r),e?e===a.filedesc&&a.initialFiledescValidated?s.when():V.askAPI("validatefiledesc",{filedesc:e},["filedesc"],a).then(()=>{a.initialFiledescValidated=!0}):s.Deferred().reject("Decription cannot be empty!").promise()),validateUrl:e=>V.askAPI("validateurl",{url:e},[]).then(e=>{if(e.entity_url)return s.Deferred().reject("This video has already been uploaded: "+e.entity_url).promise()})},k={source:()=>{var e=m.getSourceData();return oldTaskData={...r},r={...r,...e,selectedVideos:[]},s.when(v.updateFormats(e.video,e.audio,oldTaskData),v.updateUrl(e.url)).then(()=>{"playlist"===r.type?r.nextStep="playlist":r.nextStep="target"})},playlist:()=>{let a=m.getPlaylistData();var e;return 0===a.length?s.Deferred().reject("Please select at least one video to upload!").promise():(e=[...a.map(e=>()=>v.updateFilename(e.filename,e)),...a.map(e=>()=>v.updateFiledesc(e.filedesc,e)),...a.map(e=>()=>v.validateUrl(e.url))],s.when(V.runWithConcurrency(e)).then(e=>{e=e.filter(e=>!!e?.error);if(0e.error).join("\n\n")).promise();r.selectedVideos=a,r.nextStep="confirm"}))},target:()=>{var e=m.getTargetData(),a="playlist"===r.type?r.videos[r.editingVideoIndex]:r,t=(a.format=e.format,b(a,e.dateCategory));return t.valid?(a.dateCategory=t.value,a.languageCategory=e.languageCategory||null,s.when(v.updateFilename(e.filename,a),v.updateFiledesc(e.filedesc,a)).then(()=>{"playlist"===r.type?r.nextStep="playlist":r.nextStep="confirm"})):s.Deferred().reject(t.error).promise()},confirm:()=>s.when().then(()=>{r.nextStep=null})};var V=window.video2commons={init:()=>{s("#content").html(u.loading),t={},V.loadCsrf(V.checkStatus),s(window).on("beforeunload",e=>{if(n?.is(":visible"))return e.preventDefault(),e.returnValue=""});var e=/^#?!(https?:\/\/.+)/;e.test(window.location.hash)?V.addTask({url:window.location.hash.match(e)[1]}):window.location.search.slice(1)&&(l=Qs.parse(window.location.search.slice(1)),V.addTask({url:l.url}))},loadCsrf:a=>{s.get("api/csrf").done(e=>{g=e.csrf,a()})},checkStatus:()=>{o.socketio_uri&&window.WebSocket&&window.io?V.checkStatusSocket():V.checkStatusLegacy()},checkStatusSocket:()=>{var e,a,t;window.socket||(a=(e=o.socketio_uri.match(/^((?:(?:https?:)?\/\/)?[^/]+)(\/.*)$/))[1],(t=window.socket=io(a,{path:e[2]})).on("connect",()=>{s.get("api/iosession").done(e=>{t.emit("auth",{iosession:e.iosession,_csrf_token:g})})}),t.on("status",e=>{V.alterTaskTableBoilerplate(()=>{V.populateResults(e)})}),t.on("update",(e,a)=>{V.alterTaskTableBoilerplate(()=>{V.updateTask(a)})}),t.on("remove",e=>{V.alterTaskTableBoilerplate(()=>{s("#task-"+e).remove()})}))},checkStatusLegacy:()=>{window.lastStatusCheck&&clearTimeout(window.lastStatusCheck);s.get("api/status").done(e=>{V.alterTaskTableBoilerplate(()=>{V.populateResults(e)}),window.lastStatusCheck=setTimeout(V.checkStatusLegacy,5e3)}).fail(()=>{s("#content").html(u.errorDisconnect)})},setupTables:()=>{s("#content").empty(),s("#content").append(u.workers),s("#content").append(u.capacity),s("#content").append(u.utilization),s("#content").append(u.pending),s("#content").append(u.yourTasks);var e=s(u.addTask),e=(s("#content").append(e),e.click(()=>{V.addTask()}),s(u.requestServerSide));s("#content").append(e.hide())},alterTaskTableBoilerplate:e=>{s("#tasktable").length||V.setupTables();var a=window.innerHeight+window.scrollY>=document.body.offsetHeight;e(),s.isEmptyObject(t)?s("#ssubtn").addClass("disabled").hide():s("#ssubtn").removeClass("disabled").show().attr("href",V.makeSSULink(t)),a&&window.scrollTo(0,document.body.scrollHeight)},populateResults:e=>{i=e.username;var a=s("#tasktable > tbody"),t=[];s.each(e.values,(e,a)=>{V.updateTask(a),t.push(a.id)}),a.find("> tr").each(function(){var e=s(this),a=V.getTaskIDFromDOMID(e.attr("id"));t.indexOf(a)<0&&e.remove()}),e.stats?(s("#capacity").text(e.stats.processing+" / "+e.stats.capacity),s("#utilization").text(Math.round(100*e.stats.utilization)+"%"),s("#pending").text(""+e.stats.pending)):(s("#capacity").text("N/A"),s("#utilization").text("N/A"),s("#pending").text("N/A"))},updateTask:e=>{var a=s("#tasktable > tbody"),o="task-"+e.id,n=s("#"+o),a=(n.length?n.attr("status")!==e.status&&(n.html(""),V.setupTaskRow(n,o,e.status)):(s("#task-new").remove(),(n=s("")).attr({id:o,status:e.status}),a.append(n),V.setupTaskRow(n,o,e.status)),n.find(`#${o}-title`)),a=(a.text()!==e.title&&a.text(e.title),n.find(`#${o}-hostname`)),a=(a.text()!==e.hostname&&a.text(e.hostname??"N/A"),(e,a,t)=>{var i=n.find(`#${o}-statustext`);a?(a=i.html(e).find("a").attr("href",a),t&&a.text(t)):i.text()!==e&&i.text(e)});"done"===e.status?a(p.getFilter("process_link")(d.taskDone).toString(),e.url.replace("%3A",":"),e.text):"needssu"===e.status?a(p.getFilter("process_link")(d.errorTooLarge).toString(),V.makeSSULink([e])):"fail"===e.status?(a(e.text,e.url,e.url),e.restartable?n.find(`#${o}-restartbutton`).show().off().click(function(){V.eventTask(this,"restart")}):n.find(`#${o}-restartbutton`).off().hide()):a(e.text),"progress"===e.status&&V.setProgressBar(n.find(`#${o}-progress`),e.progress),"needssu"===e.status?t[e.id]=e:delete t[e.id]},setupTaskRow:(e,a,t)=>{switch(t){case"progress":e.append(s("").attr("id",a+"-title")).append(s("").attr("id",a+"-hostname")).append(s("").attr("id",a+"-status").append(s("").attr("id",a+"-statustext"))).append(s("").attr("id",a+"-progress"));var i=V.eventButton(a,"abort"),i=(e.find(`#${a}-status`).append(i),e.find(`#${a}-progress`).html(u.progressbar));V.setProgressBar(i,-1),e.removeClass("success danger");break;case"done":V.appendButtons([V.eventButton(a,"remove")],e,["danger","success"],a);break;case"fail":var i=V.eventButton(a,"remove"),o=V.eventButton(a,"restart").hide();V.appendButtons([i,o],e,["success","danger"],a);break;case"needssu":V.appendButtons([V.eventButton(a,"remove")],e,["success","danger"],a);break;case"abort":V.appendButtons([],e,["success","danger"],a)}e.attr("status",t)},makeSSULink:e=>{var a=s.map(e,e=>"* "+e.url).join("\n"),e=s.map(e,e=>`| ${e.filename} | ${e.hashsum} |`).join("\n");return"https://phabricator.wikimedia.org/maniphest/task/edit/form/106/?"+s.param({title:"Server side upload for "+i,projects:"video2commons,server-side-upload-request",description:"Please upload these file(s) to Wikimedia Commons:\n\n**URLs**\n\n{{{ urls }}}\n\n//Description files are available too: append `.txt` to the URLs.//\n\n**Checksums**\n\n| **File** | **MD5** |\n{{{ checksums }}}\n\nThank you!".replace("{{{ urls }}}",a).replace("{{{ checksums }}}",e)})},setProgressBar:(e,a)=>{e=e.find(".progress-bar");a<0?(e.addClass("progress-bar-striped active").addClass("active").text(""),a=100):e.removeClass("progress-bar-striped active").text(Math.round(a)+"%"),e.attr({"aria-valuenow":a,"aria-valuemin":"0","aria-valuemax":"100",style:`width:${a}%`})},getTaskIDFromDOMID:e=>/^(?:task-)?(.+?)(?:-(?:title|statustext|progress|abortbutton|removebutton|restartbutton))?$/.exec(e)[1],eventTask:(e,a)=>{e=s(e);e.is(".disabled")||(e.off().addClass("disabled"),V.apiPost("task/"+a,{id:V.getTaskIDFromDOMID(e.attr("id"))}).done(e=>{e.error&&window.alert(e.error),V.checkStatus()}))},setText:(e,a)=>{for(var t=0;ts(u[a+"button"]).attr("id",e+`-${a}button`).off().click(function(){V.eventTask(this,a)}),appendButtons:(e,a,t,i)=>{a.append(s("").attr("id",i+"-title"));var o=s("").attr("id",i+"-status").attr("colspan","3").append(s("").attr("id",i+"-statustext"));e.length&&o.append(e[0]);for(var n=1;n{n||((n=s("
").html(p.render("addTask.html"))).addClass("modal fade").attr({id:"addTaskDialog",role:"dialog"}),s("body").append(n),n.find("#btn-prev").html(u.prevbutton),n.find("#btn-next").html(u.nextbutton),n.find("#btn-cancel").click(()=>{V.abortUpload()}),n.find(".modal-body").keypress(e=>{13!==(e.which||e.keyCode)||s(":focus").is("textarea")||(n.find(".modal-footer #btn-next").click(),e.preventDefault())})),V.openTaskModal(e)},openTaskModal:e=>{n.find("#dialog-spinner").hide(),n.find(".modal-body").html(`
${c}
`),V.newTask(e),n.modal({backdrop:"static"}),n.on("shown.bs.modal",()=>{n.find("#url").focus()}),V.reactivatePrevNextButtons()},newTask:e=>{r={step:"source",url:"",date:"",extractor:"",audio:!0,video:!0,subtitles:!0,filename:!0,formats:[],format:"",filedesc:"",dateCategory:"",languageCategory:"",uploadedFile:{},initialUrlValidated:!1,initialFilenameValidated:!1,initialFiledescValidated:!1,nextStep:"source",history:[],videos:[],selectedVideos:[],editingVideoIndex:null},s.extend(r,e),V.setupAddTaskDialog()},setupAddTaskDialog:()=>{switch(r.step){case"source":n.find(".modal-body").html(p.render("sourceForm.html")),n.find("a#fl").attr("href","//commons.wikimedia.org/wiki/Commons:Licensing#Acceptable_licenses"),n.find("a#pd").attr("href","//commons.wikimedia.org/wiki/Commons:Licensing#Material_in_the_public_domain"),n.find("a#fu").attr("href","//commons.wikimedia.org/wiki/Commons:FU"),n.find("#url").val(r.url).on("input",function(){r.url!==s(this).val()&&(r.url=s(this).val());r.url.match(/(https?:\/\/)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)\/(watch\?.*?(?=v=)v=|embed\/|v\/|.+\?v=)?([^&=%?]{11})/)?n.find("#youtube-warning").removeClass("hidden"):n.find("#youtube-warning").addClass("hidden")}).focus(),n.find("#video").prop("checked",r.video),n.find("#audio").prop("checked",r.audio),n.find("#subtitles").prop("checked",r.subtitles),V.initUpload();break;case"playlist":n.find(".modal-body").html(p.render("playlistForm.html",{task:r})),n.find(".video-select").each((e,a)=>{e=r.videos[e];0<=r.selectedVideos.indexOf(e)&&s(a).prop("checked",!0)});var e=n.find(".video-select:checked").length===n.find(".video-select").length;n.find("#select-all").prop("checked",e),n.find("#select-all").off().change(function(){var e=s(this).is(":checked");n.find(".video-select").prop("checked",e)}),n.find(".video-select").off().change(()=>{var e=n.find(".video-select:checked").length===n.find(".video-select").length;n.find("#select-all").prop("checked",e)}),n.find(".btn-edit").off().click(function(){var e=s(this).data("video-index");r.selectedVideos=m.getPlaylistData(),r.editingVideoIndex=e,r.history.push(r.step),r.step="target",V.setupAddTaskDialog(),V.reactivatePrevNextButtons()});break;case"target":{let a="playlist"===r.type?r.videos[r.editingVideoIndex]:r;n.find(".modal-body").html(p.render("targetForm.html",{source:a})),n.find("#filename").val(a.filename.trim()).focus(),s.each(r.formats,(e,a)=>{n.find("#format").append(s("").text(a))}),n.find("#format").val(a.format),n.find("#filedesc").val(a.filedesc),n.find("#dateCategory").attr("placeholder",(e=a,t=(new Date).toISOString().split("T")[0],i=(t=e.date||t).split("-")[0],o=(e=e.audio&&!e.video)?d["datecategory-audio-placeholder"]:d["datecategory-video-placeholder"],e?o.replace("$1",i):o.replace("$1",i).replace("$2",t))).val(a.dateCategory||"");i=((o=a).audio&&!o.video?f:h)(o);s.each(i,(e,a)=>{n.find("#dateCategoryOptions").append(s("").val(a))}),n.find("#dateCategory").on("input",function(){var e=b(a,s(this).val().trim());e.valid?(n.find("#dateCategoryError").hide(),n.find("#dateCategory-group").removeClass("has-error")):(n.find("#dateCategoryError").text(e.error).show(),n.find("#dateCategory-group").addClass("has-error"))}),r.video&&r.audio?(n.find("#languageCategory-group").show(),w.forEach(e=>{n.find("#languageCategory").append(s("").val(e.category).text(e.label))}),a.languageCategory&&n.find("#languageCategory").val(a.languageCategory)):n.find("#languageCategory-group").hide();break}case"confirm":t="playlist"===r.type?p.render("playlistConfirmForm.html",{task:r}):p.render("confirmForm.html"),o=(n.find(".modal-body").html(t),[]);r.video&&o.push(d.video),r.audio&&o.push(d.audio),r.subtitles&&o.push(d.subtitles),n.find("#keep").text(o.join(", ")),V.setText(["url","extractor","filename","format"],r),n.find("#filedesc").val(r.filedesc),n.find("#btn-next").focus()}var t,i,o},reactivatePrevNextButtons:()=>{switch(n.find("#dialog-spinner").hide(),r.step){case"source":n.find("#btn-prev").addClass("disabled").off(),n.find("#btn-next").html(u.nextbutton),V.setPrevNextButton("next");break;case"playlist":V.setPrevNextButton("prev"),n.find("#btn-next").html(u.nextbutton),V.setPrevNextButton("next");break;case"target":V.setPrevNextButton("prev"),"playlist"===r.type?n.find("#btn-next").html(u.confirmbutton):n.find("#btn-next").html(u.nextbutton),V.setPrevNextButton("next");break;case"confirm":V.setPrevNextButton("prev"),n.find("#btn-next").removeClass("disabled").html(u.confirmbutton).off().click(()=>{V.disablePrevNext(!1),n.modal("hide"),s("#tasktable > tbody").append(`${c}`),window.scrollTo(0,document.body.scrollHeight),r.uploadedFile={};let a=[];if("playlist"===r.type)a=r.selectedVideos.map(e=>{let a=e.filedesc;return e.dateCategory&&(a+=` -[[Category:${e.dateCategory}]]`),e.languageCategory&&(a+=` -[[Category:${e.languageCategory}]]`),{url:e.url,extractor:e.extractor,subtitles:r.subtitles,filename:e.filename,filedesc:a,format:e.format,queue:e.queue}});else{let e=r.filedesc;r.dateCategory&&(e+=` -[[Category:${r.dateCategory}]]`),r.languageCategory&&(e+=` -[[Category:${r.languageCategory}]]`),a.push({url:r.url,extractor:r.extractor,subtitles:r.subtitles,filename:r.filename,filedesc:e,format:r.format,queue:r.queue})}V.runWithConcurrency(a.map(e=>()=>v.startTask(e).done(e=>{e.error&&window.alert(e.error)}))).always(()=>{V.checkStatus()})})}},setPrevNextButton:e=>{n.find("#btn-"+e).removeClass("disabled").off().click(()=>{V.processInput(e)})},disablePrevNext:e=>{n.find(".modal-body #dialog-errorbox").hide(),n.find("#btn-prev").addClass("disabled").off(),n.find("#btn-next").addClass("disabled").off(),e&&n.find("#dialog-spinner").show()},processInput:e=>{var a=r.step,t=k[a];t?"next"===e?(t=t(),V.promiseWorkingOn(t.done(()=>{V.transitionStep(e)}))):(V.transitionStep(e),V.reactivatePrevNextButtons()):console.error("Unknown step:",a)},transitionStep:e=>{"prev"===e&&0(V.disablePrevNext(!0),e.fail(e=>{n.find(".modal-body #dialog-errorbox").length||n.find(".modal-body").append(s('
')),n.find(".modal-body #dialog-errorbox").text("Error: "+e).show()}).always(V.reactivatePrevNextButtons)),abortUpload:(e,a)=>{e&&"pending"===e.state()&&e.reject(a),window.jqXHR?.abort&&window.jqXHR.abort()},initUpload:()=>{var i;window.jqXHR=n.find("#fileupload").fileupload({dataType:"json",formData:{_csrf_token:g},maxChunkSize:4<<20,sequentialUploads:!0}).on("fileuploadadd",(e,a)=>{window.jqXHR=a.submit(),i=s.Deferred(),V.promiseWorkingOn(i.promise()),n.find("#src-url").hide(),n.find("#src-uploading").show(),n.find("#upload-abort").off().click(()=>{V.abortUpload(i,"Upload aborted.")})}).on("fileuploadchunkdone",(e,a)=>{a.result.filekey&&(a.formData.filekey=a.result.filekey),"Continue"===a.result.result?a.result.offset!==a.uploadedBytes&&V.abortUpload(i,`Unexpected offset! Expected: ${a.uploadedBytes} Returned: `+a.result.offset):a.result.error?V.abortUpload(i,a.result.error):V.abortUpload()}).on("fileuploadprogressall",(e,a)=>{V.setProgressBar(n.find("#upload-progress"),a.loaded/a.total*100)}).on("fileuploadalways",(e,a)=>{delete a.formData.filekey,V.reactivatePrevNextButtons(),n.find("#src-url").show(),n.find("#src-uploading").hide()}).on("fileuploadfail",()=>{V.abortUpload(i,"Something went wrong while uploading... try again?")}).on("fileuploaddone",(e,a)=>{var t;"Success"===a.result.result?(t="uploads:"+a.result.filekey,r.uploadedFile[t]=a.files[0],n.find("#url").val(t),i.resolve()):V.abortUpload(i,"Upload does not seem to be successful.")})},askAPI:(e,a,i,o=null)=>{null==o&&(o=r);var n=s.Deferred();return V.apiPost(e,a).done(e=>{if(e.error)n.reject(e.error);else{for(var a=0;a{n.reject("Something weird happened. Please try again.")}),n.promise()},apiPost:(e,a)=>(a._csrf_token=g,s.post("api/"+e,a)),runWithConcurrency(e,a=5){let t=e.slice(),i=s.Deferred(),o=[],n=0,r=0,l=()=>{if(r>=t.length&&0===n)i.resolve(o);else for(;n{o[a]=e}).fail(e=>{o[a]={error:e}}).always(()=>{n--,l()})}};return l(),i.promise()}};let w=[{label:"Unselected",category:""},{label:"English (en)",category:"Videos in English"},{label:"American English (en-US)",category:"Videos in American English"},{label:"Australian English (en-AU)",category:"Videos in Australian English"},{label:"British English (en-GB)",category:"Videos in British English"},{label:"Canadian English (en-CA)",category:"Videos in Canadian English"},{label:"Spanish (es)",category:"Videos in Spanish"},{label:"Portuguese (pt)",category:"Videos in Portuguese"},{label:"Brazilian Portuguese (pt-BR)",category:"Videos in Brazilian Portuguese"},{label:"Chinese (zh)",category:"Videos in Chinese"},{label:"Cantonese (yue)",category:"Videos in Cantonese"},{label:"Mandarin (cmn)",category:"Videos in Mandarin"},{label:"Japanese (ja)",category:"Videos in Japanese"},{label:"Korean (ko)",category:"Videos in Korean"},{label:"Hindi (hi)",category:"Videos in Hindi"},{label:"Arabic (ar)",category:"Videos in Arabic"},{label:"Russian (ru)",category:"Videos in Russian"},{label:"German (de)",category:"Videos in German"},{label:"Bavarian (bar)",category:"Videos in Bavarian"},{label:"French (fr)",category:"Videos in French"},{label:"Italian (it)",category:"Videos in Italian"},{label:"Dutch (nl)",category:"Videos in Dutch"},{label:"Polish (pl)",category:"Videos in Polish"},{label:"Turkish (tr)",category:"Videos in Turkish"},{label:"Albanian (sq)",category:"Videos in Albanian"},{label:"Algerian Arabic (arq)",category:"Videos in Algerian Arabic"},{label:"Egyptian Arabic (arz)",category:"Videos in Egyptian Arabic"},{label:"Moroccan Arabic (ary)",category:"Videos in Moroccan Arabic"},{label:"North Levantine Arabic (apc)",category:"Videos in North Levantine Arabic"},{label:"Amharic (am)",category:"Videos in Amharic"},{label:"American Sign Language (ase)",category:"Videos in American Sign Language"},{label:"Ancient Greek (grc)",category:"Videos in Ancient Greek"},{label:"Armenian (hy)",category:"Videos in Armenian"},{label:"Assamese (as)",category:"Videos in Assamese"},{label:"Asturian (ast)",category:"Videos in Asturian"},{label:"Avar (av)",category:"Videos in Avar"},{label:"Azerbaijani (az)",category:"Videos in Azerbaijani"},{label:"Balinese (ban)",category:"Videos in Balinese"},{label:"Balochi (bal)",category:"Videos in Balochi"},{label:"Bangla (bn)",category:"Videos in Bangla"},{label:"Basque (eu)",category:"Videos in Basque"},{label:"Belarusian (be)",category:"Videos in Belarusian"},{label:"Bislama (bi)",category:"Videos in Bislama"},{label:"Bosnian (bs)",category:"Videos in Bosnian"},{label:"Breton (br)",category:"Videos in Breton"},{label:"British Sign Language (bfi)",category:"Videos in British Sign Language"},{label:"Bulgarian (bg)",category:"Videos in Bulgarian"},{label:"Burmese (my)",category:"Videos in Burmese"},{label:"Catalan (ca)",category:"Videos in Catalan"},{label:"Cebuano (ceb)",category:"Videos in Cebuano"},{label:"Central Bikol (bcl)",category:"Videos in Central Bikol"},{label:"Chamorro (ch)",category:"Videos in Chamorro"},{label:"Cherokee (chr)",category:"Videos in Cherokee"},{label:"Cornish (kw)",category:"Videos in Cornish"},{label:"Crimean Tatar (crh)",category:"Videos in Crimean Tatar"},{label:"Croatian (hr)",category:"Videos in Croatian"},{label:"Czech (cs)",category:"Videos in Czech"},{label:"Danish (da)",category:"Videos in Danish"},{label:"Dari (prs)",category:"Videos in Dari"},{label:"Esperanto (eo)",category:"Videos in Esperanto"},{label:"Estonian (et)",category:"Videos in Estonian"},{label:"Farsi (fa)",category:"Videos in Farsi"},{label:"Fijian (fj)",category:"Videos in Fijian"},{label:"Finnish (fi)",category:"Videos in Finnish"},{label:"Galician (gl)",category:"Videos in Galician"},{label:"Georgian (ka)",category:"Videos in Georgian"},{label:"Greek (el)",category:"Videos in Greek"},{label:"Greenlandic (kl)",category:"Videos in Greenlandic"},{label:"Guarani (gn)",category:"Videos in Guarani"},{label:"Gujarati (gu)",category:"Videos in Gujarati"},{label:"Haitian Creole (ht)",category:"Videos in Haitian Creole"},{label:"Hakka (hak)",category:"Videos in Hakka"},{label:"Min Dong Chinese (cdo)",category:"Videos in Min Dong Chinese"},{label:"Hausa (ha)",category:"Videos in Hausa"},{label:"Hebrew (he)",category:"Videos in Hebrew"},{label:"Hungarian (hu)",category:"Videos in Hungarian"},{label:"Icelandic (is)",category:"Videos in Icelandic"},{label:"Ido (io)",category:"Videos in Ido"},{label:"Igbo (ig)",category:"Videos in Igbo"},{label:"Indonesian (id)",category:"Videos in Indonesian"},{label:"Irish (ga)",category:"Videos in Irish"},{label:"Jamaican Patois (jam)",category:"Videos in Jamaican Patois"},{label:"Javanese (jv)",category:"Videos in Javanese"},{label:"Judaeo-Spanish (lad)",category:"Videos in Judaeo-Spanish"},{label:"Kannada (kn)",category:"Videos in Kannada"},{label:"Kashmiri (ks)",category:"Videos in Kashmiri"},{label:"Kazakh (kk)",category:"Videos in Kazakh"},{label:"Khasi (kha)",category:"Videos in Khasi"},{label:"Khmer (km)",category:"Videos in Khmer"},{label:"Komi (kv)",category:"Videos in Komi"},{label:"Kotava (avk)",category:"Videos in Kotava"},{label:"Kurdish (ku)",category:"Videos in Kurdish"},{label:"Kyrgyz (ky)",category:"Videos in Kyrgyz"},{label:"K'iche' (quc)",category:"Videos in K'iche'"},{label:"Lakota (lkt)",category:"Videos in Lakota"},{label:"Lao (lo)",category:"Videos in Lao"},{label:"Latin (la)",category:"Videos in Latin"},{label:"Latvian (lv)",category:"Videos in Latvian"},{label:"Leonese (roa-leon)",category:"Videos in Leonese"},{label:"Limburgish (li)",category:"Videos in Limburgish"},{label:"Lithuanian (lt)",category:"Videos in Lithuanian"},{label:"Lojban (jbo)",category:"Videos in Lojban"},{label:"Low German (nds)",category:"Videos in Low German"},{label:"Lozi (loz)",category:"Videos in Lozi"},{label:"Luxembourgish (lb)",category:"Videos in Luxembourgish"},{label:"Macedonian (mk)",category:"Videos in Macedonian"},{label:"Malay (ms)",category:"Videos in Malay"},{label:"Malayalam (ml)",category:"Videos in Malayalam"},{label:"Maltese (mt)",category:"Videos in Maltese"},{label:"Manx (gv)",category:"Videos in Manx"},{label:"Mapudungun (arn)",category:"Videos in Mapudungun"},{label:"Marathi (mr)",category:"Videos in Marathi"},{label:"Marwari (mwr)",category:"Videos in Marwari"},{label:"Mirandese (mwl)",category:"Videos in Mirandese"},{label:"Mongolian (mn)",category:"Videos in Mongolian"},{label:"Mossi (mos)",category:"Videos in Mossi"},{label:"Neapolitan (nap)",category:"Videos in Neapolitan"},{label:"Sicilian (scn)",category:"Videos in Sicilian"},{label:"Nepali (ne)",category:"Videos in Nepali"},{label:"Northern Sami (se)",category:"Videos in Northern Sami"},{label:"Norwegian (no)",category:"Videos in Norwegian"},{label:"Occitan (oc)",category:"Videos in Occitan"},{label:"Odia (or)",category:"Videos in Odia"},{label:"Pashto (ps)",category:"Videos in Pashto"},{label:"Punjabi (pa)",category:"Videos in Punjabi"},{label:"Quechua (qu)",category:"Videos in Quechua"},{label:"Romanian (ro)",category:"Videos in Romanian"},{label:"Saint Lucian Creole (acf)",category:"Videos in Saint Lucian Creole"},{label:"Samoan (sm)",category:"Videos in Samoan"},{label:"Sanskrit (sa)",category:"Videos in Sanskrit"},{label:"Santali (sat)",category:"Videos in Santali"},{label:"Sardinian (sc)",category:"Videos in Sardinian"},{label:"Serbian (sr)",category:"Videos in Serbian"},{label:"Serbo-Croatian (sh)",category:"Videos in Serbo-Croatian"},{label:"Silesian (szl)",category:"Videos in Silesian"},{label:"Sindhi (sd)",category:"Videos in Sindhi"},{label:"Slovak (sk)",category:"Videos in Slovak"},{label:"Slovene (sl)",category:"Videos in Slovene"},{label:"Somali (so)",category:"Videos in Somali"},{label:"Swahili (sw)",category:"Videos in Swahili"},{label:"Swedish (sv)",category:"Videos in Swedish"},{label:"Sylheti (syl)",category:"Videos in Sylheti"},{label:"Tagalog (tl)",category:"Videos in Tagalog"},{label:"Tamazight (zgh)",category:"Videos in Tamazight"},{label:"Tamil (ta)",category:"Videos in Tamil"},{label:"Tatar (tt)",category:"Videos in Tatar"},{label:"Telugu (te)",category:"Videos in Telugu"},{label:"Tetum (tet)",category:"Videos in Tetum"},{label:"Thai (th)",category:"Videos in Thai"},{label:"Tok Pisin (tpi)",category:"Videos in Tok Pisin"},{label:"Toki Pona (tok)",category:"Videos in Toki Pona"},{label:"Tsonga (ts)",category:"Videos in Tsonga"},{label:"Tuvan (tyv)",category:"Videos in Tuvan"},{label:"Twi (tw)",category:"Videos in Twi"},{label:"Ukrainian (uk)",category:"Videos in Ukrainian"},{label:"Urdu (ur)",category:"Videos in Urdu"},{label:"Veps (vep)",category:"Videos in Veps"},{label:"Vietnamese (vi)",category:"Videos in Vietnamese"},{label:"Volapük (vo)",category:"Videos in Volapük"},{label:"Walloon (wa)",category:"Videos in Walloon"},{label:"Welsh (cy)",category:"Videos in Welsh"},{label:"West Frisian (fy)",category:"Videos in West Frisian"},{label:"Wolof (wo)",category:"Videos in Wolof"},{label:"Xhosa (xh)",category:"Videos in Xhosa"},{label:"Yiddish (yi)",category:"Videos in Yiddish"},{label:"Yoruba (yo)",category:"Videos in Yoruba"},{label:"Yucatec (yua)",category:"Videos in Yucatec"}];s(document).ready(()=>{V.init()})})(jQuery); \ No newline at end of file diff --git a/video2commons/frontend/templates/base.min.html b/video2commons/frontend/templates/base.min.html deleted file mode 100644 index d192995..0000000 --- a/video2commons/frontend/templates/base.min.html +++ /dev/null @@ -1 +0,0 @@ -video2commons{% if lang() is rtl %}{% endif %}{% block jscss %}{% endblock %}
{% block content %}{% endblock %}
\ No newline at end of file diff --git a/video2commons/frontend/templates/error.min.html b/video2commons/frontend/templates/error.min.html deleted file mode 100644 index 69cdefb..0000000 --- a/video2commons/frontend/templates/error.min.html +++ /dev/null @@ -1 +0,0 @@ -{% extends "base.min.html" %} {% block content %} {% if html_message %}
{{ html_message|safe }} {% if stacktrace %}
{{ stacktrace }}
{% endif %}
{% else %}
{{ message }} {% if stacktrace %}
{{ stacktrace }}
{% endif %}
{% endif %} {% endblock %} \ No newline at end of file diff --git a/video2commons/frontend/templates/main.min.html b/video2commons/frontend/templates/main.min.html deleted file mode 100644 index 279431c..0000000 --- a/video2commons/frontend/templates/main.min.html +++ /dev/null @@ -1 +0,0 @@ -{% extends "base.min.html" %} {% block jscss %} {% if loggedin %}{% if config.socketio_uri %}{% endif %}{% endif %} {% endblock %} {% block content %} {% if loggedin %}{% else %}
{% endif %} {% endblock %} \ No newline at end of file