├── .prettierrc ├── .vscode ├── settings.json └── tasks.json ├── Dockerfile ├── catalog-info.yaml ├── .editorconfig ├── package.json ├── docker-compose.yml ├── routes ├── status.js └── enqueue.js ├── app.js ├── config.json.example ├── .gitignore ├── modules ├── redis_stack.js └── ffmpeg_manager.js ├── README.md ├── yarn.lock └── LICENSE.md /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": false 4 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.detectIndentation": false, 4 | "discord.enabled": true, 5 | "editor.rulers": [125] 6 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:15-alpine 2 | 3 | WORKDIR /usr/src/app 4 | COPY package*.json ./ 5 | 6 | RUN yarn install 7 | COPY . . 8 | 9 | RUN apk add --no-cache ffmpeg 10 | 11 | EXPOSE 3000 12 | CMD [ "yarn", "start" ] -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: ffmpeg-queue 5 | annotations: 6 | github.com/project-slug: uwutube/ffmpeg-queue 7 | spec: 8 | type: other 9 | lifecycle: experimental 10 | owner: uwutube 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ffmpeg-queue", 3 | "version": "0.1.1", 4 | "description": "FIFO queue server for FFmpeg, used for uwutube", 5 | "main": "app.js", 6 | "repository": "https://github.com/uwutube/ffmpeg-queue", 7 | "author": "xezno", 8 | "license": "AGPL-3.0", 9 | "scripts": { 10 | "start": "node app.js" 11 | }, 12 | "dependencies": { 13 | "fastify": "^3.9.2", 14 | "fastify-multipart": "^3.3.1", 15 | "ffmpeg": "^0.0.4", 16 | "redis": "^3.0.2", 17 | "uuid": "^8.3.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | ffmpeg_queue: 4 | container_name: ffmpeg-queue 5 | build: . 6 | ports: 7 | - "3000:3000" 8 | links: 9 | - redis 10 | volumes: 11 | - .:/usr/src/app 12 | - /usr/src/app/node_modules 13 | command: > 14 | sh -c "yarn install && 15 | yarn global add nodemon && 16 | apk update && 17 | apk add inotify-tools && 18 | nodemon start" 19 | redis: 20 | container_name: ffmpeg-queue-redis 21 | image: redis:6.0-alpine 22 | restart: always 23 | ports: 24 | - "6379:6379" -------------------------------------------------------------------------------- /routes/status.js: -------------------------------------------------------------------------------- 1 | const redis_stack = require("../modules/redis_stack"); 2 | const config = require("../config.json"); 3 | 4 | async function routes(fastify, options) { 5 | /* 6 | * Get the status of a queued job 7 | */ 8 | fastify.get("/status/:id", async (request, response) => { 9 | let pair = await redis_stack.GetId(request.params.id); 10 | return pair; 11 | }); 12 | 13 | /* 14 | * Return possible options 15 | */ 16 | fastify.options("/status*", async (request, response) => { 17 | return response.status(200).header("Allow", "GET").send(); 18 | }); 19 | } 20 | 21 | module.exports = routes; -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const config = require("./config.json"); 2 | const ffmpeg = require("./modules/ffmpeg_manager"); 3 | const fastify = require("fastify")({ 4 | logger: true 5 | }); 6 | 7 | fastify.register(require("fastify-multipart"), { 8 | fileSize: config.upload.max_file_size, 9 | files: config.upload.max_files 10 | }); 11 | 12 | fastify.register(require("./routes/enqueue")); 13 | fastify.register(require("./routes/status")); 14 | 15 | fastify.listen(3000, "0.0.0.0", function (err, address) { 16 | if (err) { 17 | fastify.log.error(err); 18 | process.exit(1); 19 | } 20 | fastify.log.info(`Server listening on ${address}`); 21 | }); -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build & run with Compose", 6 | "type": "shell", 7 | "command": "docker-compose up", 8 | "problemMatcher": [], 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | } 13 | }, 14 | { 15 | "label": "Re-build & run with Compose", 16 | "type": "shell", 17 | "command": "docker-compose up --build", 18 | "problemMatcher": [] 19 | }, 20 | { 21 | "label": "Re-build & run with Compose, re-creating volumes", 22 | "type": "shell", 23 | "command": "docker-compose up --build -V", 24 | "problemMatcher": [] 25 | }, 26 | { 27 | "label": "Build with Compose", 28 | "type": "shell", 29 | "command": "docker-compose build", 30 | "problemMatcher": [] 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "redis": { 3 | "url": "redis://redis", 4 | "queue_name": "ffmpeg-queue", 5 | "log": true 6 | }, 7 | 8 | "upload": { 9 | "max_queue_jobs": 1024, 10 | "max_file_size": 10000, 11 | "max_files": 1, 12 | 13 | "_supported_types_ack": "Many thanks to DusanBrejka - https://gist.github.com/DusanBrejka/35238dccb5cefcc804de1c5a218ee004", 14 | "supported_types": [ 15 | "video/3gpp", 16 | "video/3gpp2", 17 | "video/MP2T", 18 | "video/mp4", 19 | "video/mpeg", 20 | "video/ogg", 21 | "video/webm", 22 | "video/x-flv", 23 | "video/x-h261", 24 | "video/x-h263", 25 | "video/x-m4v", 26 | "video/x-matroska", 27 | "video/x-ms-asf", 28 | "video/x-msvideo" 29 | ] 30 | }, 31 | 32 | "ffmpeg": { 33 | "process_interval": 1000, 34 | "log": false, 35 | "configs": [ 36 | { 37 | "name": "720p", 38 | "size": "1280x720", 39 | "framerate": 60, 40 | "videoCodec": "mpeg4", 41 | "audioCodec": "aac", 42 | "audioChannels": 2, 43 | "bitrate": "2M" 44 | }, 45 | { 46 | "name": "1080p", 47 | "size": "1920x1080", 48 | "framerate": 60, 49 | "videoCodec": "mpeg4", 50 | "audioCodec": "aac", 51 | "audioChannels": 2, 52 | "bitrate": "5M" 53 | } 54 | ] 55 | } 56 | } -------------------------------------------------------------------------------- /routes/enqueue.js: -------------------------------------------------------------------------------- 1 | const redis_stack = require("../modules/redis_stack"); 2 | const config = require("../config.json"); 3 | const fs = require("fs"); 4 | const { v4: uuidv4 } = require("uuid"); 5 | 6 | async function routes(fastify, options) { 7 | /* 8 | * Push a queue job to the stack 9 | */ 10 | fastify.post("/enqueue", async (request, response) => { 11 | // Check if we've exceeded the max. number of jobs in the queue 12 | if (redis_stack.GetQueueSize() > config.upload.max_queue_jobs) { 13 | return response.status(503).send({ "status": "failure", "message": "Queue full, try again later." }); 14 | } 15 | 16 | const file = await request.file(); 17 | const data = await file.toBuffer(); 18 | 19 | // Check whether the uploaded file is actually supported by ffmpeg - if not, notify the client 20 | if (!config.upload.supported_types.includes(file.mimetype)) { 21 | return response.status(400).send({ "status": "failure", "message": "Unsupported MIME type." }); 22 | } 23 | 24 | // Get random temporary file name 25 | let fileName = `/tmp/${uuidv4()}`; 26 | 27 | // Write file contents 28 | fs.writeFile(fileName, data, (err) => { 29 | if (err) 30 | return console.error(err); 31 | 32 | console.log(`Saved file ${fileName}`); 33 | }); 34 | 35 | // File metadata - used to track the file and the time it was created, allows ffmpeg to assign itself rather than 36 | // having to assign it manually each time 37 | let fileMeta = { 38 | fileName, 39 | time: new Date() 40 | }; 41 | 42 | // Push the data itself to redis. This function handles all of the backend stuff - like creating a separate key 43 | // for the data - rather than having to do it here 44 | let queuePos = await redis_stack.Push(JSON.stringify(fileMeta)); 45 | 46 | // Send data back to client 47 | return { "status": "success", "file": fileMeta, "stack": queuePos }; 48 | }); 49 | 50 | /* 51 | * Get number of jobs on queue 52 | */ 53 | fastify.get("/enqueue", async (request, response) => { 54 | let queueSize = await redis_stack.GetQueueSize(); 55 | return response.header("Content-Type", "JSON").send(JSON.stringify({ queueSize })); 56 | }); 57 | 58 | /* 59 | * Return possible options 60 | */ 61 | fastify.options("/enqueue", async (request, response) => { 62 | return response.status(200).header("Allow", "POST, GET").send(); 63 | }); 64 | } 65 | 66 | module.exports = routes; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/yarn,node 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=yarn,node 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | .env*.local 80 | 81 | # parcel-bundler cache (https://parceljs.org/) 82 | .cache 83 | .parcel-cache 84 | 85 | # Next.js build output 86 | .next 87 | 88 | # Nuxt.js build / generate output 89 | .nuxt 90 | dist 91 | 92 | # Gatsby files 93 | .cache/ 94 | # Comment in the public line in if your project uses Gatsby and not Next.js 95 | # https://nextjs.org/blog/next-9-1#public-directory-support 96 | # public 97 | 98 | # vuepress build output 99 | .vuepress/dist 100 | 101 | # Serverless directories 102 | .serverless/ 103 | 104 | # FuseBox cache 105 | .fusebox/ 106 | 107 | # DynamoDB Local files 108 | .dynamodb/ 109 | 110 | # TernJS port file 111 | .tern-port 112 | 113 | # Stores VSCode versions used for testing VSCode extensions 114 | .vscode-test 115 | 116 | ### yarn ### 117 | # https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored 118 | 119 | .yarn/* 120 | !.yarn/releases 121 | !.yarn/plugins 122 | !.yarn/sdks 123 | !.yarn/versions 124 | 125 | # if you are NOT using Zero-installs, then: 126 | # comment the following lines 127 | !.yarn/cache 128 | 129 | # and uncomment the following lines 130 | # .pnp.* 131 | 132 | # End of https://www.toptal.com/developers/gitignore/api/yarn,node 133 | 134 | # Config files 135 | config.json -------------------------------------------------------------------------------- /modules/redis_stack.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Redis module. 3 | * Handles local connection to redis 4 | * 5 | * Singleton 6 | */ 7 | 8 | const { promisify } = require("util"); 9 | const redis = require("redis"); 10 | const config = require("../config.json"); 11 | const { v4: uuidv4 } = require("uuid"); 12 | 13 | class Redis { 14 | constructor() { 15 | this.app = redis.createClient(config.redis.url); 16 | this.app.on("error", function(err) { 17 | console.error(err); 18 | }); 19 | 20 | this._getAsync = promisify(this.app.get).bind(this.app); 21 | this._setAsync = promisify(this.app.set).bind(this.app); 22 | this._keysAsync = promisify(this.app.keys).bind(this.app); 23 | this._rpush = promisify(this.app.rpush).bind(this.app); 24 | this._rpop = promisify(this.app.rpop).bind(this.app); 25 | this._lpush = promisify(this.app.lpush).bind(this.app); 26 | this._lpop = promisify(this.app.lpop).bind(this.app); 27 | this._lrange = promisify(this.app.lrange).bind(this.app); 28 | this._delAsync = promisify(this.app.del).bind(this.app); 29 | } 30 | 31 | async GetQueueSize() { 32 | var range = await this._lrange(config.redis.queue_name, 0, -1); 33 | return range.length; 34 | } 35 | 36 | async Push(value, id) { 37 | if (!id) { 38 | // No ID provided - we're probably making a new entry. 39 | // Generate an ID 40 | id = uuidv4(); 41 | } 42 | 43 | if (config.redis.log) 44 | console.log(`Pushing ${id} to queue.`); 45 | 46 | // Push the ID to the queue 47 | let queuePos = await this._rpush(config.redis.queue_name, id); 48 | // Create a new key with the ID, give it the value of 'value'. 49 | await this._setAsync(id, value); 50 | 51 | // Return the ID just in case it was newly generated or something, and the queue pos so we can track this entry later 52 | return { id, queuePos }; 53 | } 54 | 55 | async Pop() { 56 | // Popping is a little more complex, since we have two keys to pop 57 | // We want to pop the ID (step 1) and then return the value of the pair with that key (step 2) 58 | 59 | // Pop id 60 | var id = await this._lpop(config.redis.queue_name); 61 | 62 | if (config.redis.log) 63 | console.log(`Popped ${id}.`); 64 | 65 | if (!id) // ID is null, therefore we can't get anything. 66 | return null; 67 | 68 | // Get pair 69 | var value = await this._getAsync(id); 70 | 71 | return { id, "value": JSON.parse(value) }; 72 | } 73 | 74 | async GetId(id) { 75 | // We have an ID, and we want the current status of its value 76 | if (config.redis.log) 77 | console.log(`Getting entry ${id}.`); 78 | 79 | let kvp = await this._getAsync(id); 80 | return kvp; 81 | } 82 | 83 | async SetId(value, id) { 84 | // We have an ID, and we want to set its value 85 | 86 | if (config.redis.log) 87 | console.log(`Adding entry ${id}.`); 88 | 89 | // Create a new key with the ID, give it the value of 'value'. 90 | await this._setAsync(id, value); 91 | 92 | // Return the ID just in case it was newly generated or something 93 | return { id }; 94 | } 95 | 96 | async DeleteId(id) { 97 | // Delete pair 98 | await this._delAsync(id); 99 | } 100 | } 101 | 102 | const instance = new Redis(); 103 | Object.freeze(instance); 104 | 105 | module.exports = instance; -------------------------------------------------------------------------------- /modules/ffmpeg_manager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * FFmpeg module. 3 | * Handles ffmpeg process calls, allows for video transcoding 4 | * 5 | * Singleton 6 | */ 7 | 8 | const config = require("../config.json"); 9 | const redis = require("./redis_stack"); 10 | const { spawn } = require("child_process"); 11 | 12 | class FFmpeg { 13 | constructor() { 14 | this.CheckQueue(); 15 | 16 | // Default profile values - used in place of any values that haven't been set in the config 17 | this.defaultProfile = { 18 | name: "Default", 19 | size: "1280x720", 20 | framerate: 60, 21 | videoCodec: "mpeg4", 22 | audioCodec: "aac", 23 | audioChannels: 2, 24 | bitrate: "2500kb", 25 | extension: "mp4", 26 | videoFormat: "mp4" 27 | } 28 | } 29 | 30 | /* 31 | * Invokes ffmpeg process, transcodes video based on profiles specified in config (default profile used in place of 32 | * missing values, but *not* used if there's no profiles in the config) 33 | */ 34 | async ProcessVideo(job) { 35 | console.log("Processing video"); 36 | 37 | let completeCount = 0; 38 | let path = job.value.fileName; 39 | for (let profile of config.ffmpeg.configs) { 40 | console.log(`Profile ${profile.name}`); 41 | let args = [ 42 | "-i", path, 43 | 44 | "-b:v", profile.bitrate ?? this.defaultProfile.bitrate, 45 | "-c:v", profile.videoCodec ?? this.defaultProfile.videoCodec, 46 | "-c:a", profile.audioCodec ?? this.defaultProfile.audioCodec, 47 | "-r", profile.framerate ?? this.defaultProfile.framerate, 48 | "-s", profile.size ?? this.defaultProfile.size, 49 | 50 | "-f", profile.videoFormat ?? this.defaultProfile.videoFormat, 51 | `${path}_${profile.name ?? this.defaultProfile.name}.${profile.extension ?? this.defaultProfile.extension}` 52 | ]; 53 | 54 | console.log(`ffmpeg ${args}`); 55 | 56 | let ffmpegProcess = spawn("ffmpeg", args); 57 | 58 | if (config.ffmpeg.log) { 59 | ffmpegProcess.stdout.on("data", function(data) { 60 | console.log(data); 61 | }); 62 | 63 | ffmpegProcess.stderr.setEncoding("utf8"); 64 | ffmpegProcess.stderr.on("data", function(data) { 65 | console.error(data); 66 | }); 67 | } 68 | 69 | let t = this; 70 | ffmpegProcess.on("close", function() { 71 | completeCount++; 72 | console.log(`Completed ${completeCount} encodes of ${config.ffmpeg.configs.length} for video ${path}`); 73 | t.SetStatus(job, completeCount / config.ffmpeg.configs.length); 74 | if (completeCount >= config.ffmpeg.configs.length) { 75 | console.log(`Finished processing video ${path}`); 76 | 77 | t.SetComplete(job); 78 | t.Requeue(); 79 | } 80 | }); 81 | } 82 | } 83 | 84 | /* 85 | * Check the current queue status, and process a video if there is one. 86 | */ 87 | async CheckQueue() { 88 | // This is just a separate loop in which this ffmpeg worker will assign itself to any open jobs and process a file in 89 | // the queue. 90 | // It's called from the constructor, and then just recursively calls itself until the end of time 91 | 92 | if (await redis.GetQueueSize() <= 0) { 93 | // No jobs to complete 94 | return this.Requeue(); 95 | } 96 | 97 | // Get next job 98 | let currentJob = await redis.Pop(); 99 | 100 | // Set job status (beginning to process video) 101 | await this.SetStatus(currentJob, 0); 102 | 103 | // Process video 104 | await this.ProcessVideo(currentJob); 105 | } 106 | 107 | async SetComplete(job) { 108 | // Job's complete, so we'll create an entry that says it's no longer in progress & the progress is 100% 109 | await redis.SetId(JSON.stringify(Object.assign({}, job, { "inProgress": false, "progress": 1 })), job.id); 110 | } 111 | 112 | async SetStatus(job, progress) { 113 | // Add an entry, but don't push it back onto the stack - we aren't processing this again later. 114 | // The only point of this entry is to allow us to use the /status/:id endpoint and check the transcoding status. 115 | await redis.SetId(JSON.stringify(Object.assign({}, job, { "inProgress": true, "progress": progress })), job.id); 116 | } 117 | 118 | /* 119 | * Invoke CheckQueue() after an interval specified in the config file. 120 | */ 121 | Requeue() { 122 | setTimeout(() => { this.CheckQueue() }, config.ffmpeg.process_interval); 123 | } 124 | } 125 | 126 | const instance = new FFmpeg(); 127 | Object.freeze(instance); 128 | 129 | module.exports = instance; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | uwutube banner 4 | a FIFO queue system for FFmpeg jobs for uwutube next. 5 | Documentation | 6 | Issues | 7 | Pull Requests 8 |

9 |

10 | Repo Size 11 | Contributors 12 | Stars 13 | Forks 14 | License 15 | Issues 16 |

17 |
18 | 19 | ## Dependencies 20 | 21 | - `redis` - >= 6.0 22 | 23 | ## Endpoints 24 | 25 | ### `POST /enqueue` 26 | 27 | Push a queue job to the stack. 28 | 29 | #### Parameters 30 | 31 | |Name |Type |Description| 32 | |------|------|-----------| 33 | |`file` |File |A video file to transcode (with a MIME type specified in `config.upload.supported_types`).| 34 | 35 | #### Response 36 | 37 | **JSON** 38 | |Name |Type |Description| 39 | |------|------|-----------| 40 | |`status` |Type |`success` or `failure`| 41 | |`message` |Type |If the status is `failure`, a message containing the reason as to why the request failed.| 42 | |`file` |Object |An object containing the file's given filename (`/tmp/ID`) as well as the time it was received.| 43 | |`stack` |Object |An object containing the position of this job on the stack (0 to `config.upload.max_queue_jobs`) as well as the file's given ID (generated through UUID v4).| 44 | 45 | --- 46 | 47 | ### `GET /enqueue` 48 | 49 | Get the number of jobs in the queue. 50 | 51 | #### Parameters 52 | 53 | **None** 54 | 55 | #### Response 56 | 57 | **JSON** 58 | |Name |Type |Description| 59 | |------|------|-----------| 60 | |`queueSize` |Integer|The current number of objects on the queue.| 61 | 62 | --- 63 | 64 | ### `OPTIONS /enqueue` 65 | 66 | Return a list of possible options. 67 | 68 | #### Parameters 69 | 70 | **None** 71 | 72 | #### Response 73 | 74 | **Headers** 75 | |Name |Type |Description| 76 | |------|------|-----------| 77 | |`Allow` |String|A list of possible methods for use with this endpoint.| 78 | 79 | --- 80 | 81 | ### `GET /status/:id` 82 | 83 | #### Parameters 84 | 85 | |Name |Type |Description| 86 | |------|------|-----------| 87 | |`id`|UUID|The ID of the job whose status is being requested.| 88 | 89 | #### Response 90 | 91 | **JSON** 92 | |Name |Type |Description| 93 | |------|------|-----------| 94 | |`id` |Type |Description| 95 | |`value`|Object|An object containing the file's `fileName` and the `time` it was uploaded.| 96 | |`inProgress`|Boolean|`true` if the transcode is in-progress, `false` if the transcode has completed.| 97 | |`progress`|Decimal|A value from 0 to 1 stating the current transcode progress.| 98 | 99 | --- 100 | 101 | ### `OPTIONS /status` 102 | 103 | Return a list of possible options. 104 | 105 | #### Parameters 106 | 107 | **None** 108 | 109 | #### Response 110 | 111 | **Headers** 112 | |Name |Type |Description| 113 | |------|------|-----------| 114 | |Allow |String|A list of possible methods for use with this endpoint.| 115 | 116 | ## Running Locally 117 | 118 | These instructions allow you to run ffmpeg-queue locally for development; please don't use these for production - instead, see the 119 | "Deploying" section below. 120 | 121 | ### Using Docker 122 | 123 | ffmpeg-queue has been configured for full use with Docker Compose. In order to use this, simply enter the main project directory 124 | and run `docker compose up`. 125 | 126 | ### Manually 127 | 128 | This manual method of running ffmpeg-queue is not recommended and does not include all components required to run the website. It 129 | is highly recommended that you run ffmpeg-queue using Docker. 130 | 131 | To prepare the project, enter the directory and install dependencies thru `yarn install`. 132 | To run the project, enter the directory and run `yarn start`. 133 | 134 | ## Deploying 135 | 136 | Before deploying, please: 137 | 138 | - Run `git pull` to ensure that you have the lastest version of the code, ensuring that all security fixes and patches are 139 | applied; 140 | - Ensure that all config files are filled with correct information; 141 | - Run `yarn audit` to ensure that there are no packages with security vulnerabilities. 142 | 143 | **Please be aware that none of the contributors to this project can be held responsible for data loss, pwnage / leaks, 144 | compromised accounts, etc.** 145 | 146 | ### Using Docker 147 | 148 | TODO 149 | 150 | ### Manually 151 | 152 | TODO 153 | 154 | ## License 155 | 156 | Unless otherwise stated, files in this repository are provided under the `GNU AGPLv3`: 157 | 158 | ``` 159 | ffmpeg-queue - a simple FIFO queue system for FFmpeg jobs 160 | 161 | Copyright (C) 2021 Alex Guthrie 162 | 163 | This program is free software: you can redistribute it and/or modify 164 | it under the terms of the GNU Affero General Public License as published by 165 | the Free Software Foundation, either version 3 of the License, or 166 | (at your option) any later version. 167 | 168 | This program is distributed in the hope that it will be useful, 169 | but WITHOUT ANY WARRANTY; without even the implied warranty of 170 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 171 | GNU Affero General Public License for more details. 172 | 173 | You should have received a copy of the GNU Affero General Public License 174 | along with this program. If not, see . 175 | ``` 176 | 177 | In addition to these terms, as per `Section 7` of the license, the following additional terms are in effect: 178 | 179 | ``` 180 | Trademark use 181 | 182 | This license does not grant any rights to use any trademarks 183 | associated with this program, including the trademark "uwutube", any 184 | logos associated with "uwutube" (such as the "uwutube" logo), or any 185 | other "uwutube" trademarks. This license also does not grant 186 | authorization to use the "uwutube" name or the names of any 187 | contributor for any purpose without written permission. 188 | ``` 189 | 190 | A copy of the `GNU AGPLv3` is available within `LICENSE.md`, or at [gnu.org](https://www.gnu.org/licenses/#AGPL). 191 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abstract-logging@^2.0.0: 6 | version "2.0.1" 7 | resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" 8 | integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== 9 | 10 | ajv@^6.11.0, ajv@^6.12.2: 11 | version "6.12.6" 12 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" 13 | integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== 14 | dependencies: 15 | fast-deep-equal "^3.1.1" 16 | fast-json-stable-stringify "^2.0.0" 17 | json-schema-traverse "^0.4.1" 18 | uri-js "^4.2.2" 19 | 20 | archy@^1.0.0: 21 | version "1.0.0" 22 | resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" 23 | integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= 24 | 25 | atomic-sleep@^1.0.0: 26 | version "1.0.0" 27 | resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" 28 | integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== 29 | 30 | avvio@^7.1.2: 31 | version "7.2.0" 32 | resolved "https://registry.yarnpkg.com/avvio/-/avvio-7.2.0.tgz#b4bf4eaf4a0207a4e6a58a7859207250793cc81b" 33 | integrity sha512-KtC63UyZARidAoIV8wXutAZnDIbZcXBqLjTAhZOX+mdMZBQCh5il/15MvCvma1178nhTwvN2D0TOAdiKG1MpUA== 34 | dependencies: 35 | archy "^1.0.0" 36 | debug "^4.0.0" 37 | fastq "^1.6.1" 38 | queue-microtask "^1.1.2" 39 | 40 | busboy@^0.3.1: 41 | version "0.3.1" 42 | resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" 43 | integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw== 44 | dependencies: 45 | dicer "0.3.0" 46 | 47 | cookie@^0.4.0: 48 | version "0.4.1" 49 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" 50 | integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== 51 | 52 | debug@^4.0.0: 53 | version "4.3.1" 54 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" 55 | integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== 56 | dependencies: 57 | ms "2.1.2" 58 | 59 | deepmerge@^4.2.2: 60 | version "4.2.2" 61 | resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" 62 | integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== 63 | 64 | denque@^1.4.1: 65 | version "1.5.0" 66 | resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de" 67 | integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ== 68 | 69 | dicer@0.3.0: 70 | version "0.3.0" 71 | resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" 72 | integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== 73 | dependencies: 74 | streamsearch "0.1.2" 75 | 76 | end-of-stream@^1.4.4: 77 | version "1.4.4" 78 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 79 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 80 | dependencies: 81 | once "^1.4.0" 82 | 83 | fast-decode-uri-component@^1.0.1: 84 | version "1.0.1" 85 | resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543" 86 | integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== 87 | 88 | fast-deep-equal@^3.1.1: 89 | version "3.1.3" 90 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 91 | integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 92 | 93 | fast-json-stable-stringify@^2.0.0: 94 | version "2.1.0" 95 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" 96 | integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== 97 | 98 | fast-json-stringify@^2.2.1: 99 | version "2.3.1" 100 | resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-2.3.1.tgz#694221a1d2ae08416903c0ede9309466220cfb2f" 101 | integrity sha512-uWKa6TWxlh11TESMOd+SdB+Y/xvGT8hi+a9rm+4D8WvSxRikAj7n5BJeq0ZC6snKi0wXK4t69izYUzfZoJ6gqg== 102 | dependencies: 103 | ajv "^6.11.0" 104 | deepmerge "^4.2.2" 105 | string-similarity "^4.0.1" 106 | 107 | fast-redact@^3.0.0: 108 | version "3.0.0" 109 | resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.0.0.tgz#ac2f9e36c9f4976f5db9fb18c6ffbaf308cf316d" 110 | integrity sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w== 111 | 112 | fast-safe-stringify@^2.0.7: 113 | version "2.0.7" 114 | resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" 115 | integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== 116 | 117 | fastify-error@^0.2.0: 118 | version "0.2.0" 119 | resolved "https://registry.yarnpkg.com/fastify-error/-/fastify-error-0.2.0.tgz#9a1c28d4f42b6259e7a549671c8e5e2d85660634" 120 | integrity sha512-zabxsBatj59ROG0fhP36zNdc5Q1/eYeH9oSF9uvfrurZf8/JKfrJbMcIGrLpLWcf89rS6L91RHWm20A/X85hcA== 121 | 122 | fastify-multipart@^3.3.1: 123 | version "3.3.1" 124 | resolved "https://registry.yarnpkg.com/fastify-multipart/-/fastify-multipart-3.3.1.tgz#1a74ec9c81fb8dbae2281eb2d84870dedb0c0b8f" 125 | integrity sha512-66fwP97W5eihJGoYJisoJ+K7Mk7Fj0YYsdTLdESefVfi78C2FwbDQcYE4JWH27ucCpLvd199KUpClhQsWD8Dbg== 126 | dependencies: 127 | busboy "^0.3.1" 128 | deepmerge "^4.2.2" 129 | end-of-stream "^1.4.4" 130 | fastify-error "^0.2.0" 131 | fastify-plugin "^3.0.0" 132 | hexoid "^1.0.0" 133 | stream-wormhole "^1.1.0" 134 | 135 | fastify-plugin@^3.0.0: 136 | version "3.0.0" 137 | resolved "https://registry.yarnpkg.com/fastify-plugin/-/fastify-plugin-3.0.0.tgz#cf1b8c8098e3b5a7c8c30e6aeb06903370c054ca" 138 | integrity sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w== 139 | 140 | fastify-warning@^0.2.0: 141 | version "0.2.0" 142 | resolved "https://registry.yarnpkg.com/fastify-warning/-/fastify-warning-0.2.0.tgz#e717776026a4493dc9a2befa44db6d17f618008f" 143 | integrity sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw== 144 | 145 | fastify@^3.9.2: 146 | version "3.9.2" 147 | resolved "https://registry.yarnpkg.com/fastify/-/fastify-3.9.2.tgz#20007d1a558cf8c88a8ef2936b4e484553fdf706" 148 | integrity sha512-8VEf4l6a2tdtrYQQwgT9Zh2/OvOgE4Sf6cDNMduAnKZ44CXrT0tXuH/uQkXuxqMAW27ZOy4jeexBl8CFaECntg== 149 | dependencies: 150 | abstract-logging "^2.0.0" 151 | ajv "^6.12.2" 152 | avvio "^7.1.2" 153 | fast-json-stringify "^2.2.1" 154 | fastify-error "^0.2.0" 155 | fastify-warning "^0.2.0" 156 | find-my-way "^3.0.5" 157 | flatstr "^1.0.12" 158 | light-my-request "^4.2.0" 159 | pino "^6.2.1" 160 | proxy-addr "^2.0.5" 161 | readable-stream "^3.4.0" 162 | rfdc "^1.1.4" 163 | secure-json-parse "^2.0.0" 164 | semver "^7.3.2" 165 | tiny-lru "^7.0.0" 166 | 167 | fastq@^1.6.1: 168 | version "1.10.0" 169 | resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.10.0.tgz#74dbefccade964932cdf500473ef302719c652bb" 170 | integrity sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA== 171 | dependencies: 172 | reusify "^1.0.4" 173 | 174 | ffmpeg@^0.0.4: 175 | version "0.0.4" 176 | resolved "https://registry.yarnpkg.com/ffmpeg/-/ffmpeg-0.0.4.tgz#1c460df8e7da5127f62ceef4bfa06c59c89630cb" 177 | integrity sha1-HEYN+OfaUSf2LO70v6BsWciWMMs= 178 | dependencies: 179 | when ">= 0.0.1" 180 | 181 | find-my-way@^3.0.5: 182 | version "3.0.5" 183 | resolved "https://registry.yarnpkg.com/find-my-way/-/find-my-way-3.0.5.tgz#f71c5ef1b4865401e1b97ba428121a8f55439eec" 184 | integrity sha512-FweGg0cv1sBX8z7WhvBX5B5AECW4Zdh/NiB38Oa0qwSNIyPgRBCl/YjxuZn/rz38E/MMBHeVKJ22i7W3c626Gg== 185 | dependencies: 186 | fast-decode-uri-component "^1.0.1" 187 | safe-regex2 "^2.0.0" 188 | semver-store "^0.3.0" 189 | 190 | flatstr@^1.0.12: 191 | version "1.0.12" 192 | resolved "https://registry.yarnpkg.com/flatstr/-/flatstr-1.0.12.tgz#c2ba6a08173edbb6c9640e3055b95e287ceb5931" 193 | integrity sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw== 194 | 195 | forwarded@~0.1.2: 196 | version "0.1.2" 197 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 198 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 199 | 200 | hexoid@^1.0.0: 201 | version "1.0.0" 202 | resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" 203 | integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== 204 | 205 | inherits@^2.0.3: 206 | version "2.0.4" 207 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 208 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 209 | 210 | ipaddr.js@1.9.1: 211 | version "1.9.1" 212 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 213 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 214 | 215 | json-schema-traverse@^0.4.1: 216 | version "0.4.1" 217 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 218 | integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== 219 | 220 | light-my-request@^4.2.0: 221 | version "4.4.1" 222 | resolved "https://registry.yarnpkg.com/light-my-request/-/light-my-request-4.4.1.tgz#bfa2220316eef4f6465bf2f0667780da6b7f6a71" 223 | integrity sha512-FDNRF2mYjthIRWE7O8d/X7AzDx4otQHl4/QXbu3Q/FRwBFcgb+ZoDaUd5HwN53uQXLAiw76osN+Va0NEaOW6rQ== 224 | dependencies: 225 | ajv "^6.12.2" 226 | cookie "^0.4.0" 227 | fastify-warning "^0.2.0" 228 | readable-stream "^3.6.0" 229 | set-cookie-parser "^2.4.1" 230 | 231 | lru-cache@^6.0.0: 232 | version "6.0.0" 233 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 234 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 235 | dependencies: 236 | yallist "^4.0.0" 237 | 238 | ms@2.1.2: 239 | version "2.1.2" 240 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 241 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 242 | 243 | once@^1.4.0: 244 | version "1.4.0" 245 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 246 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 247 | dependencies: 248 | wrappy "1" 249 | 250 | pino-std-serializers@^3.1.0: 251 | version "3.1.1" 252 | resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-3.1.1.tgz#5a9a00715c9ff3428d5555d4b023a3f8a5893e54" 253 | integrity sha512-Ud4+WzgEr/swOlfQdkxqKP2hlwuQlIqQ5YwYpo9MczNg2mpsKKyKgwQXW5KnP5tgH3/Cy9QRhAhLM8k1K+gfnA== 254 | 255 | pino@^6.2.1: 256 | version "6.10.0" 257 | resolved "https://registry.yarnpkg.com/pino/-/pino-6.10.0.tgz#0dc4c701aef9e6f1ab276c7c1fbfcbebeba720db" 258 | integrity sha512-ZFGE/Wq930gFb1h0RI6S/QOfkyzNj94Xubwlyo4XpxNUgrG1C0iEqnlooG5Fymx6yrUUtEJ8j/u8NCGwgwTXaQ== 259 | dependencies: 260 | fast-redact "^3.0.0" 261 | fast-safe-stringify "^2.0.7" 262 | flatstr "^1.0.12" 263 | pino-std-serializers "^3.1.0" 264 | quick-format-unescaped "^4.0.1" 265 | sonic-boom "^1.0.2" 266 | 267 | proxy-addr@^2.0.5: 268 | version "2.0.6" 269 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" 270 | integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== 271 | dependencies: 272 | forwarded "~0.1.2" 273 | ipaddr.js "1.9.1" 274 | 275 | punycode@^2.1.0: 276 | version "2.1.1" 277 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 278 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 279 | 280 | queue-microtask@^1.1.2: 281 | version "1.2.2" 282 | resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.2.tgz#abf64491e6ecf0f38a6502403d4cda04f372dfd3" 283 | integrity sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg== 284 | 285 | quick-format-unescaped@^4.0.1: 286 | version "4.0.1" 287 | resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz#437a5ea1a0b61deb7605f8ab6a8fd3858dbeb701" 288 | integrity sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A== 289 | 290 | readable-stream@^3.4.0, readable-stream@^3.6.0: 291 | version "3.6.0" 292 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 293 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 294 | dependencies: 295 | inherits "^2.0.3" 296 | string_decoder "^1.1.1" 297 | util-deprecate "^1.0.1" 298 | 299 | redis-commands@^1.5.0: 300 | version "1.6.0" 301 | resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.6.0.tgz#36d4ca42ae9ed29815cdb30ad9f97982eba1ce23" 302 | integrity sha512-2jnZ0IkjZxvguITjFTrGiLyzQZcTvaw8DAaCXxZq/dsHXz7KfMQ3OUJy7Tz9vnRtZRVz6VRCPDvruvU8Ts44wQ== 303 | 304 | redis-errors@^1.0.0, redis-errors@^1.2.0: 305 | version "1.2.0" 306 | resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" 307 | integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= 308 | 309 | redis-parser@^3.0.0: 310 | version "3.0.0" 311 | resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" 312 | integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= 313 | dependencies: 314 | redis-errors "^1.0.0" 315 | 316 | redis@^3.0.2: 317 | version "3.0.2" 318 | resolved "https://registry.yarnpkg.com/redis/-/redis-3.0.2.tgz#bd47067b8a4a3e6a2e556e57f71cc82c7360150a" 319 | integrity sha512-PNhLCrjU6vKVuMOyFu7oSP296mwBkcE6lrAjruBYG5LgdSqtRBoVQIylrMyVZD/lkF24RSNNatzvYag6HRBHjQ== 320 | dependencies: 321 | denque "^1.4.1" 322 | redis-commands "^1.5.0" 323 | redis-errors "^1.2.0" 324 | redis-parser "^3.0.0" 325 | 326 | ret@~0.2.0: 327 | version "0.2.2" 328 | resolved "https://registry.yarnpkg.com/ret/-/ret-0.2.2.tgz#b6861782a1f4762dce43402a71eb7a283f44573c" 329 | integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ== 330 | 331 | reusify@^1.0.4: 332 | version "1.0.4" 333 | resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 334 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 335 | 336 | rfdc@^1.1.4: 337 | version "1.1.4" 338 | resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" 339 | integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== 340 | 341 | safe-buffer@~5.2.0: 342 | version "5.2.1" 343 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 344 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 345 | 346 | safe-regex2@^2.0.0: 347 | version "2.0.0" 348 | resolved "https://registry.yarnpkg.com/safe-regex2/-/safe-regex2-2.0.0.tgz#b287524c397c7a2994470367e0185e1916b1f5b9" 349 | integrity sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ== 350 | dependencies: 351 | ret "~0.2.0" 352 | 353 | secure-json-parse@^2.0.0: 354 | version "2.2.0" 355 | resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.2.0.tgz#cc53338a0c59667d22ef5847b11dbfffcb4b8a37" 356 | integrity sha512-OYpk8nU1g9+5u6HZ+OOMZVpD037Jo5E9QzdDdQRe6b9ZPWOoB85AenHz5Rd90UwG8zdef69dO0axSosdBsDK9Q== 357 | 358 | semver-store@^0.3.0: 359 | version "0.3.0" 360 | resolved "https://registry.yarnpkg.com/semver-store/-/semver-store-0.3.0.tgz#ce602ff07df37080ec9f4fb40b29576547befbe9" 361 | integrity sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg== 362 | 363 | semver@^7.3.2: 364 | version "7.3.4" 365 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" 366 | integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== 367 | dependencies: 368 | lru-cache "^6.0.0" 369 | 370 | set-cookie-parser@^2.4.1: 371 | version "2.4.6" 372 | resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.6.tgz#43bdea028b9e6f176474ee5298e758b4a44799c3" 373 | integrity sha512-mNCnTUF0OYPwYzSHbdRdCfNNHqrne+HS5tS5xNb6yJbdP9wInV0q5xPLE0EyfV/Q3tImo3y/OXpD8Jn0Jtnjrg== 374 | 375 | sonic-boom@^1.0.2: 376 | version "1.3.0" 377 | resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-1.3.0.tgz#5c77c846ce6c395dddf2eb8e8e65f9cc576f2e76" 378 | integrity sha512-4nX6OYvOYr6R76xfQKi6cZpTO3YSWe/vd+QdIfoH0lBy0MnPkeAbb2rRWgmgADkXUeCKPwO1FZAKlAVWAadELw== 379 | dependencies: 380 | atomic-sleep "^1.0.0" 381 | flatstr "^1.0.12" 382 | 383 | stream-wormhole@^1.1.0: 384 | version "1.1.0" 385 | resolved "https://registry.yarnpkg.com/stream-wormhole/-/stream-wormhole-1.1.0.tgz#300aff46ced553cfec642a05251885417693c33d" 386 | integrity sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew== 387 | 388 | streamsearch@0.1.2: 389 | version "0.1.2" 390 | resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" 391 | integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= 392 | 393 | string-similarity@^4.0.1: 394 | version "4.0.4" 395 | resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" 396 | integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== 397 | 398 | string_decoder@^1.1.1: 399 | version "1.3.0" 400 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 401 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 402 | dependencies: 403 | safe-buffer "~5.2.0" 404 | 405 | tiny-lru@^7.0.0: 406 | version "7.0.6" 407 | resolved "https://registry.yarnpkg.com/tiny-lru/-/tiny-lru-7.0.6.tgz#b0c3cdede1e5882aa2d1ae21cb2ceccf2a331f24" 408 | integrity sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow== 409 | 410 | uri-js@^4.2.2: 411 | version "4.4.1" 412 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" 413 | integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== 414 | dependencies: 415 | punycode "^2.1.0" 416 | 417 | util-deprecate@^1.0.1: 418 | version "1.0.2" 419 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 420 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 421 | 422 | uuid@^8.3.2: 423 | version "8.3.2" 424 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" 425 | integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== 426 | 427 | "when@>= 0.0.1": 428 | version "3.7.8" 429 | resolved "https://registry.yarnpkg.com/when/-/when-3.7.8.tgz#c7130b6a7ea04693e842cdc9e7a1f2aa39a39f82" 430 | integrity sha1-xxMLan6gRpPoQs3J56Hyqjmjn4I= 431 | 432 | wrappy@1: 433 | version "1.0.2" 434 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 435 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 436 | 437 | yallist@^4.0.0: 438 | version "4.0.0" 439 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 440 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 441 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU Affero General Public License 2 | ================================= 3 | 4 | _Version 3, 19 November 2007_ 5 | _Copyright © 2007 Free Software Foundation, Inc. <>_ 6 | 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | ## Preamble 11 | 12 | The GNU Affero General Public License is a free, copyleft license for 13 | software and other kinds of works, specifically designed to ensure 14 | cooperation with the community in the case of network server software. 15 | 16 | The licenses for most software and other practical works are designed 17 | to take away your freedom to share and change the works. By contrast, 18 | our General Public Licenses are intended to guarantee your freedom to 19 | share and change all versions of a program--to make sure it remains free 20 | software for all its users. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | Developers that use our General Public Licenses protect your rights 30 | with two steps: **(1)** assert copyright on the software, and **(2)** offer 31 | you this License which gives you legal permission to copy, distribute 32 | and/or modify the software. 33 | 34 | A secondary benefit of defending all users' freedom is that 35 | improvements made in alternate versions of the program, if they 36 | receive widespread use, become available for other developers to 37 | incorporate. Many developers of free software are heartened and 38 | encouraged by the resulting cooperation. However, in the case of 39 | software used on network servers, this result may fail to come about. 40 | The GNU General Public License permits making a modified version and 41 | letting the public access it on a server without ever releasing its 42 | source code to the public. 43 | 44 | The GNU Affero General Public License is designed specifically to 45 | ensure that, in such cases, the modified source code becomes available 46 | to the community. It requires the operator of a network server to 47 | provide the source code of the modified version running there to the 48 | users of that server. Therefore, public use of a modified version, on 49 | a publicly accessible server, gives the public access to the source 50 | code of the modified version. 51 | 52 | An older license, called the Affero General Public License and 53 | published by Affero, was designed to accomplish similar goals. This is 54 | a different license, not a version of the Affero GPL, but Affero has 55 | released a new version of the Affero GPL which permits relicensing under 56 | this license. 57 | 58 | The precise terms and conditions for copying, distribution and 59 | modification follow. 60 | 61 | ## TERMS AND CONDITIONS 62 | 63 | ### 0. Definitions 64 | 65 | “This License” refers to version 3 of the GNU Affero General Public License. 66 | 67 | “Copyright” also means copyright-like laws that apply to other kinds of 68 | works, such as semiconductor masks. 69 | 70 | “The Program” refers to any copyrightable work licensed under this 71 | License. Each licensee is addressed as “you”. “Licensees” and 72 | “recipients” may be individuals or organizations. 73 | 74 | To “modify” a work means to copy from or adapt all or part of the work 75 | in a fashion requiring copyright permission, other than the making of an 76 | exact copy. The resulting work is called a “modified version” of the 77 | earlier work or a work “based on” the earlier work. 78 | 79 | A “covered work” means either the unmodified Program or a work based 80 | on the Program. 81 | 82 | To “propagate” a work means to do anything with it that, without 83 | permission, would make you directly or secondarily liable for 84 | infringement under applicable copyright law, except executing it on a 85 | computer or modifying a private copy. Propagation includes copying, 86 | distribution (with or without modification), making available to the 87 | public, and in some countries other activities as well. 88 | 89 | To “convey” a work means any kind of propagation that enables other 90 | parties to make or receive copies. Mere interaction with a user through 91 | a computer network, with no transfer of a copy, is not conveying. 92 | 93 | An interactive user interface displays “Appropriate Legal Notices” 94 | to the extent that it includes a convenient and prominently visible 95 | feature that **(1)** displays an appropriate copyright notice, and **(2)** 96 | tells the user that there is no warranty for the work (except to the 97 | extent that warranties are provided), that licensees may convey the 98 | work under this License, and how to view a copy of this License. If 99 | the interface presents a list of user commands or options, such as a 100 | menu, a prominent item in the list meets this criterion. 101 | 102 | ### 1. Source Code 103 | 104 | The “source code” for a work means the preferred form of the work 105 | for making modifications to it. “Object code” means any non-source 106 | form of a work. 107 | 108 | A “Standard Interface” means an interface that either is an official 109 | standard defined by a recognized standards body, or, in the case of 110 | interfaces specified for a particular programming language, one that 111 | is widely used among developers working in that language. 112 | 113 | The “System Libraries” of an executable work include anything, other 114 | than the work as a whole, that **(a)** is included in the normal form of 115 | packaging a Major Component, but which is not part of that Major 116 | Component, and **(b)** serves only to enable use of the work with that 117 | Major Component, or to implement a Standard Interface for which an 118 | implementation is available to the public in source code form. A 119 | “Major Component”, in this context, means a major essential component 120 | (kernel, window system, and so on) of the specific operating system 121 | (if any) on which the executable work runs, or a compiler used to 122 | produce the work, or an object code interpreter used to run it. 123 | 124 | The “Corresponding Source” for a work in object code form means all 125 | the source code needed to generate, install, and (for an executable 126 | work) run the object code and to modify the work, including scripts to 127 | control those activities. However, it does not include the work's 128 | System Libraries, or general-purpose tools or generally available free 129 | programs which are used unmodified in performing those activities but 130 | which are not part of the work. For example, Corresponding Source 131 | includes interface definition files associated with source files for 132 | the work, and the source code for shared libraries and dynamically 133 | linked subprograms that the work is specifically designed to require, 134 | such as by intimate data communication or control flow between those 135 | subprograms and other parts of the work. 136 | 137 | The Corresponding Source need not include anything that users 138 | can regenerate automatically from other parts of the Corresponding 139 | Source. 140 | 141 | The Corresponding Source for a work in source code form is that 142 | same work. 143 | 144 | ### 2. Basic Permissions 145 | 146 | All rights granted under this License are granted for the term of 147 | copyright on the Program, and are irrevocable provided the stated 148 | conditions are met. This License explicitly affirms your unlimited 149 | permission to run the unmodified Program. The output from running a 150 | covered work is covered by this License only if the output, given its 151 | content, constitutes a covered work. This License acknowledges your 152 | rights of fair use or other equivalent, as provided by copyright law. 153 | 154 | You may make, run and propagate covered works that you do not 155 | convey, without conditions so long as your license otherwise remains 156 | in force. You may convey covered works to others for the sole purpose 157 | of having them make modifications exclusively for you, or provide you 158 | with facilities for running those works, provided that you comply with 159 | the terms of this License in conveying all material for which you do 160 | not control copyright. Those thus making or running the covered works 161 | for you must do so exclusively on your behalf, under your direction 162 | and control, on terms that prohibit them from making any copies of 163 | your copyrighted material outside their relationship with you. 164 | 165 | Conveying under any other circumstances is permitted solely under 166 | the conditions stated below. Sublicensing is not allowed; section 10 167 | makes it unnecessary. 168 | 169 | ### 3. Protecting Users' Legal Rights From Anti-Circumvention Law 170 | 171 | No covered work shall be deemed part of an effective technological 172 | measure under any applicable law fulfilling obligations under article 173 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 174 | similar laws prohibiting or restricting circumvention of such 175 | measures. 176 | 177 | When you convey a covered work, you waive any legal power to forbid 178 | circumvention of technological measures to the extent such circumvention 179 | is effected by exercising rights under this License with respect to 180 | the covered work, and you disclaim any intention to limit operation or 181 | modification of the work as a means of enforcing, against the work's 182 | users, your or third parties' legal rights to forbid circumvention of 183 | technological measures. 184 | 185 | ### 4. Conveying Verbatim Copies 186 | 187 | You may convey verbatim copies of the Program's source code as you 188 | receive it, in any medium, provided that you conspicuously and 189 | appropriately publish on each copy an appropriate copyright notice; 190 | keep intact all notices stating that this License and any 191 | non-permissive terms added in accord with section 7 apply to the code; 192 | keep intact all notices of the absence of any warranty; and give all 193 | recipients a copy of this License along with the Program. 194 | 195 | You may charge any price or no price for each copy that you convey, 196 | and you may offer support or warranty protection for a fee. 197 | 198 | ### 5. Conveying Modified Source Versions 199 | 200 | You may convey a work based on the Program, or the modifications to 201 | produce it from the Program, in the form of source code under the 202 | terms of section 4, provided that you also meet all of these conditions: 203 | 204 | * **a)** The work must carry prominent notices stating that you modified 205 | it, and giving a relevant date. 206 | * **b)** The work must carry prominent notices stating that it is 207 | released under this License and any conditions added under section 7. 208 | This requirement modifies the requirement in section 4 to 209 | “keep intact all notices”. 210 | * **c)** You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | * **d)** If the work has interactive user interfaces, each must display 218 | Appropriate Legal Notices; however, if the Program has interactive 219 | interfaces that do not display Appropriate Legal Notices, your 220 | work need not make them do so. 221 | 222 | A compilation of a covered work with other separate and independent 223 | works, which are not by their nature extensions of the covered work, 224 | and which are not combined with it such as to form a larger program, 225 | in or on a volume of a storage or distribution medium, is called an 226 | “aggregate” if the compilation and its resulting copyright are not 227 | used to limit the access or legal rights of the compilation's users 228 | beyond what the individual works permit. Inclusion of a covered work 229 | in an aggregate does not cause this License to apply to the other 230 | parts of the aggregate. 231 | 232 | ### 6. Conveying Non-Source Forms 233 | 234 | You may convey a covered work in object code form under the terms 235 | of sections 4 and 5, provided that you also convey the 236 | machine-readable Corresponding Source under the terms of this License, 237 | in one of these ways: 238 | 239 | * **a)** Convey the object code in, or embodied in, a physical product 240 | (including a physical distribution medium), accompanied by the 241 | Corresponding Source fixed on a durable physical medium 242 | customarily used for software interchange. 243 | * **b)** Convey the object code in, or embodied in, a physical product 244 | (including a physical distribution medium), accompanied by a 245 | written offer, valid for at least three years and valid for as 246 | long as you offer spare parts or customer support for that product 247 | model, to give anyone who possesses the object code either **(1)** a 248 | copy of the Corresponding Source for all the software in the 249 | product that is covered by this License, on a durable physical 250 | medium customarily used for software interchange, for a price no 251 | more than your reasonable cost of physically performing this 252 | conveying of source, or **(2)** access to copy the 253 | Corresponding Source from a network server at no charge. 254 | * **c)** Convey individual copies of the object code with a copy of the 255 | written offer to provide the Corresponding Source. This 256 | alternative is allowed only occasionally and noncommercially, and 257 | only if you received the object code with such an offer, in accord 258 | with subsection 6b. 259 | * **d)** Convey the object code by offering access from a designated 260 | place (gratis or for a charge), and offer equivalent access to the 261 | Corresponding Source in the same way through the same place at no 262 | further charge. You need not require recipients to copy the 263 | Corresponding Source along with the object code. If the place to 264 | copy the object code is a network server, the Corresponding Source 265 | may be on a different server (operated by you or a third party) 266 | that supports equivalent copying facilities, provided you maintain 267 | clear directions next to the object code saying where to find the 268 | Corresponding Source. Regardless of what server hosts the 269 | Corresponding Source, you remain obligated to ensure that it is 270 | available for as long as needed to satisfy these requirements. 271 | * **e)** Convey the object code using peer-to-peer transmission, provided 272 | you inform other peers where the object code and Corresponding 273 | Source of the work are being offered to the general public at no 274 | charge under subsection 6d. 275 | 276 | A separable portion of the object code, whose source code is excluded 277 | from the Corresponding Source as a System Library, need not be 278 | included in conveying the object code work. 279 | 280 | A “User Product” is either **(1)** a “consumer product”, which means any 281 | tangible personal property which is normally used for personal, family, 282 | or household purposes, or **(2)** anything designed or sold for incorporation 283 | into a dwelling. In determining whether a product is a consumer product, 284 | doubtful cases shall be resolved in favor of coverage. For a particular 285 | product received by a particular user, “normally used” refers to a 286 | typical or common use of that class of product, regardless of the status 287 | of the particular user or of the way in which the particular user 288 | actually uses, or expects or is expected to use, the product. A product 289 | is a consumer product regardless of whether the product has substantial 290 | commercial, industrial or non-consumer uses, unless such uses represent 291 | the only significant mode of use of the product. 292 | 293 | “Installation Information” for a User Product means any methods, 294 | procedures, authorization keys, or other information required to install 295 | and execute modified versions of a covered work in that User Product from 296 | a modified version of its Corresponding Source. The information must 297 | suffice to ensure that the continued functioning of the modified object 298 | code is in no case prevented or interfered with solely because 299 | modification has been made. 300 | 301 | If you convey an object code work under this section in, or with, or 302 | specifically for use in, a User Product, and the conveying occurs as 303 | part of a transaction in which the right of possession and use of the 304 | User Product is transferred to the recipient in perpetuity or for a 305 | fixed term (regardless of how the transaction is characterized), the 306 | Corresponding Source conveyed under this section must be accompanied 307 | by the Installation Information. But this requirement does not apply 308 | if neither you nor any third party retains the ability to install 309 | modified object code on the User Product (for example, the work has 310 | been installed in ROM). 311 | 312 | The requirement to provide Installation Information does not include a 313 | requirement to continue to provide support service, warranty, or updates 314 | for a work that has been modified or installed by the recipient, or for 315 | the User Product in which it has been modified or installed. Access to a 316 | network may be denied when the modification itself materially and 317 | adversely affects the operation of the network or violates the rules and 318 | protocols for communication across the network. 319 | 320 | Corresponding Source conveyed, and Installation Information provided, 321 | in accord with this section must be in a format that is publicly 322 | documented (and with an implementation available to the public in 323 | source code form), and must require no special password or key for 324 | unpacking, reading or copying. 325 | 326 | ### 7. Additional Terms 327 | 328 | “Additional permissions” are terms that supplement the terms of this 329 | License by making exceptions from one or more of its conditions. 330 | Additional permissions that are applicable to the entire Program shall 331 | be treated as though they were included in this License, to the extent 332 | that they are valid under applicable law. If additional permissions 333 | apply only to part of the Program, that part may be used separately 334 | under those permissions, but the entire Program remains governed by 335 | this License without regard to the additional permissions. 336 | 337 | When you convey a copy of a covered work, you may at your option 338 | remove any additional permissions from that copy, or from any part of 339 | it. (Additional permissions may be written to require their own 340 | removal in certain cases when you modify the work.) You may place 341 | additional permissions on material, added by you to a covered work, 342 | for which you have or can give appropriate copyright permission. 343 | 344 | Notwithstanding any other provision of this License, for material you 345 | add to a covered work, you may (if authorized by the copyright holders of 346 | that material) supplement the terms of this License with terms: 347 | 348 | * **a)** Disclaiming warranty or limiting liability differently from the 349 | terms of sections 15 and 16 of this License; or 350 | * **b)** Requiring preservation of specified reasonable legal notices or 351 | author attributions in that material or in the Appropriate Legal 352 | Notices displayed by works containing it; or 353 | * **c)** Prohibiting misrepresentation of the origin of that material, or 354 | requiring that modified versions of such material be marked in 355 | reasonable ways as different from the original version; or 356 | * **d)** Limiting the use for publicity purposes of names of licensors or 357 | authors of the material; or 358 | * **e)** Declining to grant rights under trademark law for use of some 359 | trade names, trademarks, or service marks; or 360 | * **f)** Requiring indemnification of licensors and authors of that 361 | material by anyone who conveys the material (or modified versions of 362 | it) with contractual assumptions of liability to the recipient, for 363 | any liability that these contractual assumptions directly impose on 364 | those licensors and authors. 365 | 366 | All other non-permissive additional terms are considered “further 367 | restrictions” within the meaning of section 10. If the Program as you 368 | received it, or any part of it, contains a notice stating that it is 369 | governed by this License along with a term that is a further 370 | restriction, you may remove that term. If a license document contains 371 | a further restriction but permits relicensing or conveying under this 372 | License, you may add to a covered work material governed by the terms 373 | of that license document, provided that the further restriction does 374 | not survive such relicensing or conveying. 375 | 376 | If you add terms to a covered work in accord with this section, you 377 | must place, in the relevant source files, a statement of the 378 | additional terms that apply to those files, or a notice indicating 379 | where to find the applicable terms. 380 | 381 | Additional terms, permissive or non-permissive, may be stated in the 382 | form of a separately written license, or stated as exceptions; 383 | the above requirements apply either way. 384 | 385 | ### 8. Termination 386 | 387 | You may not propagate or modify a covered work except as expressly 388 | provided under this License. Any attempt otherwise to propagate or 389 | modify it is void, and will automatically terminate your rights under 390 | this License (including any patent licenses granted under the third 391 | paragraph of section 11). 392 | 393 | However, if you cease all violation of this License, then your 394 | license from a particular copyright holder is reinstated **(a)** 395 | provisionally, unless and until the copyright holder explicitly and 396 | finally terminates your license, and **(b)** permanently, if the copyright 397 | holder fails to notify you of the violation by some reasonable means 398 | prior to 60 days after the cessation. 399 | 400 | Moreover, your license from a particular copyright holder is 401 | reinstated permanently if the copyright holder notifies you of the 402 | violation by some reasonable means, this is the first time you have 403 | received notice of violation of this License (for any work) from that 404 | copyright holder, and you cure the violation prior to 30 days after 405 | your receipt of the notice. 406 | 407 | Termination of your rights under this section does not terminate the 408 | licenses of parties who have received copies or rights from you under 409 | this License. If your rights have been terminated and not permanently 410 | reinstated, you do not qualify to receive new licenses for the same 411 | material under section 10. 412 | 413 | ### 9. Acceptance Not Required for Having Copies 414 | 415 | You are not required to accept this License in order to receive or 416 | run a copy of the Program. Ancillary propagation of a covered work 417 | occurring solely as a consequence of using peer-to-peer transmission 418 | to receive a copy likewise does not require acceptance. However, 419 | nothing other than this License grants you permission to propagate or 420 | modify any covered work. These actions infringe copyright if you do 421 | not accept this License. Therefore, by modifying or propagating a 422 | covered work, you indicate your acceptance of this License to do so. 423 | 424 | ### 10. Automatic Licensing of Downstream Recipients 425 | 426 | Each time you convey a covered work, the recipient automatically 427 | receives a license from the original licensors, to run, modify and 428 | propagate that work, subject to this License. You are not responsible 429 | for enforcing compliance by third parties with this License. 430 | 431 | An “entity transaction” is a transaction transferring control of an 432 | organization, or substantially all assets of one, or subdividing an 433 | organization, or merging organizations. If propagation of a covered 434 | work results from an entity transaction, each party to that 435 | transaction who receives a copy of the work also receives whatever 436 | licenses to the work the party's predecessor in interest had or could 437 | give under the previous paragraph, plus a right to possession of the 438 | Corresponding Source of the work from the predecessor in interest, if 439 | the predecessor has it or can get it with reasonable efforts. 440 | 441 | You may not impose any further restrictions on the exercise of the 442 | rights granted or affirmed under this License. For example, you may 443 | not impose a license fee, royalty, or other charge for exercise of 444 | rights granted under this License, and you may not initiate litigation 445 | (including a cross-claim or counterclaim in a lawsuit) alleging that 446 | any patent claim is infringed by making, using, selling, offering for 447 | sale, or importing the Program or any portion of it. 448 | 449 | ### 11. Patents 450 | 451 | A “contributor” is a copyright holder who authorizes use under this 452 | License of the Program or a work on which the Program is based. The 453 | work thus licensed is called the contributor's “contributor version”. 454 | 455 | A contributor's “essential patent claims” are all patent claims 456 | owned or controlled by the contributor, whether already acquired or 457 | hereafter acquired, that would be infringed by some manner, permitted 458 | by this License, of making, using, or selling its contributor version, 459 | but do not include claims that would be infringed only as a 460 | consequence of further modification of the contributor version. For 461 | purposes of this definition, “control” includes the right to grant 462 | patent sublicenses in a manner consistent with the requirements of 463 | this License. 464 | 465 | Each contributor grants you a non-exclusive, worldwide, royalty-free 466 | patent license under the contributor's essential patent claims, to 467 | make, use, sell, offer for sale, import and otherwise run, modify and 468 | propagate the contents of its contributor version. 469 | 470 | In the following three paragraphs, a “patent license” is any express 471 | agreement or commitment, however denominated, not to enforce a patent 472 | (such as an express permission to practice a patent or covenant not to 473 | sue for patent infringement). To “grant” such a patent license to a 474 | party means to make such an agreement or commitment not to enforce a 475 | patent against the party. 476 | 477 | If you convey a covered work, knowingly relying on a patent license, 478 | and the Corresponding Source of the work is not available for anyone 479 | to copy, free of charge and under the terms of this License, through a 480 | publicly available network server or other readily accessible means, 481 | then you must either **(1)** cause the Corresponding Source to be so 482 | available, or **(2)** arrange to deprive yourself of the benefit of the 483 | patent license for this particular work, or **(3)** arrange, in a manner 484 | consistent with the requirements of this License, to extend the patent 485 | license to downstream recipients. “Knowingly relying” means you have 486 | actual knowledge that, but for the patent license, your conveying the 487 | covered work in a country, or your recipient's use of the covered work 488 | in a country, would infringe one or more identifiable patents in that 489 | country that you have reason to believe are valid. 490 | 491 | If, pursuant to or in connection with a single transaction or 492 | arrangement, you convey, or propagate by procuring conveyance of, a 493 | covered work, and grant a patent license to some of the parties 494 | receiving the covered work authorizing them to use, propagate, modify 495 | or convey a specific copy of the covered work, then the patent license 496 | you grant is automatically extended to all recipients of the covered 497 | work and works based on it. 498 | 499 | A patent license is “discriminatory” if it does not include within 500 | the scope of its coverage, prohibits the exercise of, or is 501 | conditioned on the non-exercise of one or more of the rights that are 502 | specifically granted under this License. You may not convey a covered 503 | work if you are a party to an arrangement with a third party that is 504 | in the business of distributing software, under which you make payment 505 | to the third party based on the extent of your activity of conveying 506 | the work, and under which the third party grants, to any of the 507 | parties who would receive the covered work from you, a discriminatory 508 | patent license **(a)** in connection with copies of the covered work 509 | conveyed by you (or copies made from those copies), or **(b)** primarily 510 | for and in connection with specific products or compilations that 511 | contain the covered work, unless you entered into that arrangement, 512 | or that patent license was granted, prior to 28 March 2007. 513 | 514 | Nothing in this License shall be construed as excluding or limiting 515 | any implied license or other defenses to infringement that may 516 | otherwise be available to you under applicable patent law. 517 | 518 | ### 12. No Surrender of Others' Freedom 519 | 520 | If conditions are imposed on you (whether by court order, agreement or 521 | otherwise) that contradict the conditions of this License, they do not 522 | excuse you from the conditions of this License. If you cannot convey a 523 | covered work so as to satisfy simultaneously your obligations under this 524 | License and any other pertinent obligations, then as a consequence you may 525 | not convey it at all. For example, if you agree to terms that obligate you 526 | to collect a royalty for further conveying from those to whom you convey 527 | the Program, the only way you could satisfy both those terms and this 528 | License would be to refrain entirely from conveying the Program. 529 | 530 | ### 13. Remote Network Interaction; Use with the GNU General Public License 531 | 532 | Notwithstanding any other provision of this License, if you modify the 533 | Program, your modified version must prominently offer all users 534 | interacting with it remotely through a computer network (if your version 535 | supports such interaction) an opportunity to receive the Corresponding 536 | Source of your version by providing access to the Corresponding Source 537 | from a network server at no charge, through some standard or customary 538 | means of facilitating copying of software. This Corresponding Source 539 | shall include the Corresponding Source for any work covered by version 3 540 | of the GNU General Public License that is incorporated pursuant to the 541 | following paragraph. 542 | 543 | Notwithstanding any other provision of this License, you have 544 | permission to link or combine any covered work with a work licensed 545 | under version 3 of the GNU General Public License into a single 546 | combined work, and to convey the resulting work. The terms of this 547 | License will continue to apply to the part which is the covered work, 548 | but the work with which it is combined will remain governed by version 549 | 3 of the GNU General Public License. 550 | 551 | ### 14. Revised Versions of this License 552 | 553 | The Free Software Foundation may publish revised and/or new versions of 554 | the GNU Affero General Public License from time to time. Such new versions 555 | will be similar in spirit to the present version, but may differ in detail to 556 | address new problems or concerns. 557 | 558 | Each version is given a distinguishing version number. If the 559 | Program specifies that a certain numbered version of the GNU Affero General 560 | Public License “or any later version” applies to it, you have the 561 | option of following the terms and conditions either of that numbered 562 | version or of any later version published by the Free Software 563 | Foundation. If the Program does not specify a version number of the 564 | GNU Affero General Public License, you may choose any version ever published 565 | by the Free Software Foundation. 566 | 567 | If the Program specifies that a proxy can decide which future 568 | versions of the GNU Affero General Public License can be used, that proxy's 569 | public statement of acceptance of a version permanently authorizes you 570 | to choose that version for the Program. 571 | 572 | Later license versions may give you additional or different 573 | permissions. However, no additional obligations are imposed on any 574 | author or copyright holder as a result of your choosing to follow a 575 | later version. 576 | 577 | ### 15. Disclaimer of Warranty 578 | 579 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 580 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 581 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY 582 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 583 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 584 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 585 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 586 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 587 | 588 | ### 16. Limitation of Liability 589 | 590 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 591 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 592 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 593 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 594 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 595 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 596 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 597 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 598 | SUCH DAMAGES. 599 | 600 | ### 17. Interpretation of Sections 15 and 16 601 | 602 | If the disclaimer of warranty and limitation of liability provided 603 | above cannot be given local legal effect according to their terms, 604 | reviewing courts shall apply local law that most closely approximates 605 | an absolute waiver of all civil liability in connection with the 606 | Program, unless a warranty or assumption of liability accompanies a 607 | copy of the Program in return for a fee. 608 | 609 | _END OF TERMS AND CONDITIONS_ 610 | 611 | ## How to Apply These Terms to Your New Programs 612 | 613 | If you develop a new program, and you want it to be of the greatest 614 | possible use to the public, the best way to achieve this is to make it 615 | free software which everyone can redistribute and change under these terms. 616 | 617 | To do so, attach the following notices to the program. It is safest 618 | to attach them to the start of each source file to most effectively 619 | state the exclusion of warranty; and each file should have at least 620 | the “copyright” line and a pointer to where the full notice is found. 621 | 622 | 623 | Copyright (C) 624 | 625 | This program is free software: you can redistribute it and/or modify 626 | it under the terms of the GNU Affero General Public License as published by 627 | the Free Software Foundation, either version 3 of the License, or 628 | (at your option) any later version. 629 | 630 | This program is distributed in the hope that it will be useful, 631 | but WITHOUT ANY WARRANTY; without even the implied warranty of 632 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 633 | GNU Affero General Public License for more details. 634 | 635 | You should have received a copy of the GNU Affero General Public License 636 | along with this program. If not, see . 637 | 638 | Also add information on how to contact you by electronic and paper mail. 639 | 640 | If your software can interact with users remotely through a computer 641 | network, you should also make sure that it provides a way for users to 642 | get its source. For example, if your program is a web application, its 643 | interface could display a “Source” link that leads users to an archive 644 | of the code. There are many ways you could offer source, and different 645 | solutions will be better for different programs; see section 13 for the 646 | specific requirements. 647 | 648 | You should also get your employer (if you work as a programmer) or school, 649 | if any, to sign a “copyright disclaimer” for the program, if necessary. 650 | For more information on this, and how to apply and follow the GNU AGPL, see 651 | <>. --------------------------------------------------------------------------------