From 0594d34f1b1168035de4ca49807f43b6c0c9b3f8 Mon Sep 17 00:00:00 2001 From: Josh5 Date: Mon, 18 Sep 2023 20:09:31 +0000 Subject: [PATCH] deploy: df8b7ff425659a30c73a35c1c85819f889139ce1 --- 404.html | 4 ++-- assets/js/{8fb0903d.3157c004.js => 8fb0903d.2fb0c1b7.js} | 2 +- .../{runtime~main.f9e608e9.js => runtime~main.98e2da68.js} | 2 +- docs/advanced/docker_compose_cifs_mounts/index.html | 4 ++-- .../advanced/hardware_accelerated_encoding_vaapi/index.html | 4 ++-- docs/configuration/libraries/adding_libraries/index.html | 4 ++-- docs/configuration/libraries/configure_libraries/index.html | 4 ++-- docs/configuration/libraries/file_testing/index.html | 4 ++-- docs/configuration/libraries/library_scanner/index.html | 4 ++-- docs/configuration/libraries/managing_task_list/index.html | 4 ++-- docs/configuration/link_settings/index.html | 4 ++-- .../plugins/adding_a_custom_plugin_repo/index.html | 4 ++-- docs/configuration/plugins/configuring_plugins/index.html | 4 ++-- docs/configuration/plugins/installing_plugins/index.html | 4 ++-- docs/configuration/plugins/overview/index.html | 4 ++-- docs/configuration/workers_settings/index.html | 4 ++-- docs/dashboard/completed_tasks/index.html | 4 ++-- docs/dashboard/pending_tasks/index.html | 4 ++-- docs/dashboard/workers/index.html | 4 ++-- docs/development/developing_plugins/index.html | 4 ++-- docs/development/plugin_manager_cli/index.html | 4 ++-- .../plugin_repos/creating_a_pull_request/index.html | 4 ++-- .../plugin_repos/creating_your_own_repo/index.html | 4 ++-- .../writing_plugins/creating_a_new_plugin/index.html | 4 ++-- docs/development/writing_plugins/introduction/index.html | 6 +++--- .../writing_plugins/plugin_runner_types/index.html | 4 ++-- docs/development/writing_plugins/plugin_settings/index.html | 4 ++-- docs/development/writing_plugins/system_info/index.html | 4 ++-- docs/guides/filebot_post_processor/index.html | 4 ++-- docs/guides/nvidia_support_unmanic_on_linux/index.html | 4 ++-- docs/guides/script_subtitle_stripping/index.html | 4 ++-- docs/guides/unmanic_kubernetes_install/index.html | 4 ++-- docs/guides/unmanic_link_installations/index.html | 4 ++-- docs/guides/unmanic_macos_install/index.html | 4 ++-- docs/guides/unmanic_windows_install/index.html | 4 ++-- docs/index.html | 4 ++-- docs/installation/docker/index.html | 4 ++-- docs/installation/pip/index.html | 4 ++-- docs/installation/synology/index.html | 4 ++-- docs/installation/unraid/index.html | 4 ++-- docs/requirements/index.html | 4 ++-- index.html | 4 ++-- stats/index.html | 4 ++-- 43 files changed, 85 insertions(+), 85 deletions(-) rename assets/js/{8fb0903d.3157c004.js => 8fb0903d.2fb0c1b7.js} (99%) rename assets/js/{runtime~main.f9e608e9.js => runtime~main.98e2da68.js} (67%) diff --git a/404.html b/404.html index 267db79..6eb0072 100644 --- a/404.html +++ b/404.html @@ -4,13 +4,13 @@ Page Not Found | Unmanic - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/8fb0903d.3157c004.js b/assets/js/8fb0903d.2fb0c1b7.js similarity index 99% rename from assets/js/8fb0903d.3157c004.js rename to assets/js/8fb0903d.2fb0c1b7.js index 222495b..fa927b4 100644 --- a/assets/js/8fb0903d.3157c004.js +++ b/assets/js/8fb0903d.2fb0c1b7.js @@ -1 +1 @@ -"use strict";(self.webpackChunkunmanic_documentation=self.webpackChunkunmanic_documentation||[]).push([[6488],{3905:(e,t,n)=>{n.d(t,{Zo:()=>s,kt:()=>g});var i=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var p=i.createContext({}),u=function(e){var t=i.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},s=function(e){var t=u(e.components);return i.createElement(p.Provider,{value:t},e.children)},d="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},c=i.forwardRef((function(e,t){var n=e.components,a=e.mdxType,r=e.originalType,p=e.parentName,s=o(e,["components","mdxType","originalType","parentName"]),d=u(n),c=a,g=d["".concat(p,".").concat(c)]||d[c]||m[c]||r;return n?i.createElement(g,l(l({ref:t},s),{},{components:n})):i.createElement(g,l({ref:t},s))}));function g(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=n.length,l=new Array(r);l[0]=c;var o={};for(var p in t)hasOwnProperty.call(t,p)&&(o[p]=t[p]);o.originalType=e,o[d]="string"==typeof e?e:a,l[1]=o;for(var u=2;u{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>o,toc:()=>u});var i=n(7462),a=(n(7294),n(3905));const r={title:"Introduction to Writing Plugins",id:"introduction"},l=void 0,o={unversionedId:"development/writing_plugins/introduction",id:"development/writing_plugins/introduction",title:"Introduction to Writing Plugins",description:"Writing Plugins for Unmanic should be easy.",source:"@site/docs/development/writing_plugins/introduction.mdx",sourceDirName:"development/writing_plugins",slug:"/development/writing_plugins/introduction",permalink:"/docs/development/writing_plugins/introduction",draft:!1,tags:[],version:"current",frontMatter:{title:"Introduction to Writing Plugins",id:"introduction"},sidebar:"docs",previous:{title:"Plugin Development Overview",permalink:"/docs/development/developing_plugins"},next:{title:"Creating a New Plugin",permalink:"/docs/development/writing_plugins/creating_a_new_plugin"}},p={},u=[{value:"Directory Structure",id:"directory-structure",level:2},{value:"File: changelog.md",id:"file-changelogmd",level:3},{value:"File: description.json (optional)",id:"file-descriptionjson-optional",level:3},{value:"File: info.json",id:"file-infojson",level:3},{value:"Example:",id:"example",level:5},{value:"Plugin handler compatibility:",id:"plugin-handler-compatibility",level:5},{value:"Directory: lib (optional)",id:"directory-lib-optional",level:3},{value:"File: package.json (optional)",id:"file-packagejson-optional",level:3},{value:"File: plugin.py",id:"file-pluginpy",level:3},{value:"File: requirements.txt (optional)",id:"file-requirementstxt-optional",level:3},{value:"Directory: static (optional)",id:"directory-static-optional",level:3},{value:"Plugin Module",id:"plugin-module",level:2},{value:"Runners",id:"runners",level:4},{value:"Example:",id:"example-1",level:5}],s={toc:u};function d(e){let{components:t,...n}=e;return(0,a.kt)("wrapper",(0,i.Z)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Writing Plugins for Unmanic should be easy. "),(0,a.kt)("p",null,"All that is required to get started is a basic knowledge of writing\nPython scripts."),(0,a.kt)("h2",{id:"directory-structure"},"Directory Structure"),(0,a.kt)("p",null,"The basic directory structure of a Plugin should be:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-sh"},"my_new_plugin\n\u251c\u2500\u2500 changelog.md\n\u251c\u2500\u2500 description.md\n\u251c\u2500\u2500 info.json\n\u251c\u2500\u2500 lib\n\u251c\u2500\u2500 package.json\n\u251c\u2500\u2500 plugin.py\n\u251c\u2500\u2500 requirements.txt\n\u2514\u2500\u2500 static\n")),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-changelogmd"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"changelog.md")),(0,a.kt)("p",null,"This file should be used to record changes made to the Plugin."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-descriptionjson-optional"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"description.json")," ",(0,a.kt)("em",{parentName:"h3"},"(optional)")),(0,a.kt)("p",null,"This file can be used to extend the description provided within the ",(0,a.kt)("em",{parentName:"p"},"info.json")," file."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-infojson"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"info.json")),(0,a.kt)("p",null,"A JSON file containing the metadata of your Plugin.\nThis JSON file should contain the following data:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"author"))," - Your name."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"compatibility"))," - A list of Unmanic Plugin Handler versions that this Plugin is compatible with (see table below)."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"description"))," - A long description of your Plugin detailing what it does. To include line-breaks, insert a ",(0,a.kt)("inlineCode",{parentName:"li"},"\\n"),"."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"icon"))," - A URI to an image. If no icon is provided, the default Plugin icon will be used in the WebUI."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"id"))," - A string identifier for your Plugin. Should only contain lowercase ",(0,a.kt)("inlineCode",{parentName:"li"},"a-z")," or ",(0,a.kt)("inlineCode",{parentName:"li"},"_")," characters."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"name"))," - A very short name for your Plugin."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"platform"))," - Platform compatibility of this plugin. Can be any combination of ",'["all", "linux", "windows", "mac"]',"."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"priorities"))," - A dictionary of the initial priority that this Plugin's runners within the Plugin flow."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"tags"))," - A comma separated list able to be used as keywords when searching for Plugins."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"version"))," - The version of your Plugin.")),(0,a.kt)("h5",{id:"example"},"Example:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-json"},'{\n "author": "Josh.5",\n "compatibility": [\n 1,\n 2\n ],\n "description": "Specify a language tag for Unmanic to try and put as 1st subtitle track.",\n "icon": "https://raw.githubusercontent.com/Josh5/unmanic.plugin.reorder_subtitle_streams_by_language/master/icon.png",\n "id": "reorder_subtitle_streams_by_language",\n "name": "Re-order subtitle streams by language",\n "platform": [\n "all"\n ],\n "priorities": {\n "on_library_management_file_test": 99,\n "on_worker_process": 2\n },\n "tags": "subtitle,ffmpeg,library file test",\n "version": "1.0.4"\n}\n')),(0,a.kt)("h5",{id:"plugin-handler-compatibility"},"Plugin handler compatibility:"),(0,a.kt)("table",null,(0,a.kt)("thead",{parentName:"table"},(0,a.kt)("tr",{parentName:"thead"},(0,a.kt)("th",{parentName:"tr",align:null},"Plugin handler"),(0,a.kt)("th",{parentName:"tr",align:null},"Unmanic versions"))),(0,a.kt)("tbody",{parentName:"table"},(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"v1"),(0,a.kt)("td",{parentName:"tr",align:null},"v0.1.0 - v0.1.4")),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"v2"),(0,a.kt)("td",{parentName:"tr",align:null},"v0.2.0 - current")))),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"directory-lib-optional"},"Directory: ",(0,a.kt)("inlineCode",{parentName:"h3"},"lib")," ",(0,a.kt)("em",{parentName:"h3"},"(optional)")),(0,a.kt)("p",null,"Location of any additional Python modules imported by this plugin.\nUse this directory if you are including files from another project on GitHub, etc."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-packagejson-optional"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"package.json")," ",(0,a.kt)("em",{parentName:"h3"},"(optional)")),(0,a.kt)("p",null,"This file will be read during the plugin build in GitHub. Any nodeJS packages listed will be installed and packaged with the plugin."),(0,a.kt)("p",null,"Python modules will be installed to ",(0,a.kt)("inlineCode",{parentName:"p"},"{plugin root}/node_modules"),"."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-pluginpy"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"plugin.py")),(0,a.kt)("p",null,"The main Pugin Python module."),(0,a.kt)("p",null,"This file will be imported and it's functions called byt he main Unmanic process."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-requirementstxt-optional"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"requirements.txt")," ",(0,a.kt)("em",{parentName:"h3"},"(optional)")),(0,a.kt)("p",null,"This file will be read during the plugin build in GitHub. Any Python modules listed will be installed and packaged with the plugin."),(0,a.kt)("p",null,"Python modules will be installed to ",(0,a.kt)("inlineCode",{parentName:"p"},"{plugin root}/site-packages"),"."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"directory-static-optional"},"Directory: ",(0,a.kt)("inlineCode",{parentName:"h3"},"static")," ",(0,a.kt)("em",{parentName:"h3"},"(optional)")),(0,a.kt)("p",null,"Location of any static assets to be served via the webserver."),(0,a.kt)("p",null,"Assets located in this directory will be available via the webserver at ",(0,a.kt)("inlineCode",{parentName:"p"},"/unmanic/panel/{plugin ID}/static/(.*)"),"."),(0,a.kt)("h2",{id:"plugin-module"},"Plugin Module"),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"plugin.py")," file is a stand-alone Python module. From this module you may import other\nmodules as you see fit. There is no limitation on what may be executed within the\n",(0,a.kt)("a",{parentName:"p",href:"#runners"},(0,a.kt)("strong",{parentName:"a"},"runner")),"\nof your Plugin."),(0,a.kt)("hr",null),(0,a.kt)("h4",{id:"runners"},"Runners"),(0,a.kt)("p",null,"The Plugin module is made up of defined ",(0,a.kt)("strong",{parentName:"p"},"runner")," functions. A Plugin may include multiple\nrunner functions such that it is executed at multiple stages of the library optimisation process."),(0,a.kt)("p",null,"A ",(0,a.kt)("strong",{parentName:"p"},"data")," parameter is provided to the runner functions. This data parameter is a dictionary\nobject of information pertaining to that stage of the library optimisation process.\nThis data object's schema that is provided to a plugin runner should still exist once the plugin runner function has completed execution.\nDuring the function, that data may be manipulated as you see fit.\nA plugin runner may fail if data is removed from that data dictionary."),(0,a.kt)("p",null,"Plugins should be tested with the ",(0,a.kt)("a",{parentName:"p",href:"/docs/development/plugin_manager_cli"},(0,a.kt)("strong",{parentName:"a"},"Plugin Manager CLI"))," before publishing changes to ensure\nthat the returned data matches the required schema for all included runner functions."),(0,a.kt)("h5",{id:"example-1"},"Example:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-python"},"def on_worker_process(data):\n ...\n return\n")),(0,a.kt)("table",null,(0,a.kt)("thead",{parentName:"table"},(0,a.kt)("tr",{parentName:"thead"},(0,a.kt)("th",{parentName:"tr",align:null},"Runner Type"),(0,a.kt)("th",{parentName:"tr",align:null},"Documentation"))),(0,a.kt)("tbody",{parentName:"table"},(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"on_library_management_file_test"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#library-management---file-test"},"LINK"))),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"on_worker_process"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#worker---processing-file"},"LINK"))),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"on_postprocessor_file_movement"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#post-processor---file-movements"},"LINK"))),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"on_postprocessor_task_results"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#post-processor---marking-task-successfailure"},"LINK"))),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"render_frontend_panel"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#frontend---data-panel"},"LINK"))),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"render_plugin_api"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#frontend---plugin-api"},"LINK"))))))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkunmanic_documentation=self.webpackChunkunmanic_documentation||[]).push([[6488],{3905:(e,t,n)=>{n.d(t,{Zo:()=>s,kt:()=>g});var i=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var p=i.createContext({}),u=function(e){var t=i.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},s=function(e){var t=u(e.components);return i.createElement(p.Provider,{value:t},e.children)},d="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},c=i.forwardRef((function(e,t){var n=e.components,a=e.mdxType,r=e.originalType,p=e.parentName,s=o(e,["components","mdxType","originalType","parentName"]),d=u(n),c=a,g=d["".concat(p,".").concat(c)]||d[c]||m[c]||r;return n?i.createElement(g,l(l({ref:t},s),{},{components:n})):i.createElement(g,l({ref:t},s))}));function g(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=n.length,l=new Array(r);l[0]=c;var o={};for(var p in t)hasOwnProperty.call(t,p)&&(o[p]=t[p]);o.originalType=e,o[d]="string"==typeof e?e:a,l[1]=o;for(var u=2;u{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>o,toc:()=>u});var i=n(7462),a=(n(7294),n(3905));const r={title:"Introduction to Writing Plugins",id:"introduction"},l=void 0,o={unversionedId:"development/writing_plugins/introduction",id:"development/writing_plugins/introduction",title:"Introduction to Writing Plugins",description:"Writing Plugins for Unmanic should be easy.",source:"@site/docs/development/writing_plugins/introduction.mdx",sourceDirName:"development/writing_plugins",slug:"/development/writing_plugins/introduction",permalink:"/docs/development/writing_plugins/introduction",draft:!1,tags:[],version:"current",frontMatter:{title:"Introduction to Writing Plugins",id:"introduction"},sidebar:"docs",previous:{title:"Plugin Development Overview",permalink:"/docs/development/developing_plugins"},next:{title:"Creating a New Plugin",permalink:"/docs/development/writing_plugins/creating_a_new_plugin"}},p={},u=[{value:"Directory Structure",id:"directory-structure",level:2},{value:"File: changelog.md",id:"file-changelogmd",level:3},{value:"File: description.json (optional)",id:"file-descriptionjson-optional",level:3},{value:"File: info.json",id:"file-infojson",level:3},{value:"Example:",id:"example",level:5},{value:"Plugin handler compatibility:",id:"plugin-handler-compatibility",level:5},{value:"Directory: lib (optional)",id:"directory-lib-optional",level:3},{value:"File: package.json (optional)",id:"file-packagejson-optional",level:3},{value:"File: plugin.py",id:"file-pluginpy",level:3},{value:"File: requirements.txt (optional)",id:"file-requirementstxt-optional",level:3},{value:"Directory: static (optional)",id:"directory-static-optional",level:3},{value:"Plugin Module",id:"plugin-module",level:2},{value:"Runners",id:"runners",level:4},{value:"Example:",id:"example-1",level:5}],s={toc:u};function d(e){let{components:t,...n}=e;return(0,a.kt)("wrapper",(0,i.Z)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Writing Plugins for Unmanic should be easy. "),(0,a.kt)("p",null,"All that is required to get started is a basic knowledge of writing\nPython scripts."),(0,a.kt)("h2",{id:"directory-structure"},"Directory Structure"),(0,a.kt)("p",null,"The basic directory structure of a Plugin should be:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-sh"},"my_new_plugin\n\u251c\u2500\u2500 changelog.md\n\u251c\u2500\u2500 description.md\n\u251c\u2500\u2500 info.json\n\u251c\u2500\u2500 lib\n\u251c\u2500\u2500 package.json\n\u251c\u2500\u2500 plugin.py\n\u251c\u2500\u2500 requirements.txt\n\u2514\u2500\u2500 static\n")),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-changelogmd"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"changelog.md")),(0,a.kt)("p",null,"This file should be used to record changes made to the Plugin."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-descriptionjson-optional"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"description.json")," ",(0,a.kt)("em",{parentName:"h3"},"(optional)")),(0,a.kt)("p",null,"This file can be used to extend the description provided within the ",(0,a.kt)("em",{parentName:"p"},"info.json")," file."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-infojson"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"info.json")),(0,a.kt)("p",null,"A JSON file containing the metadata of your Plugin.\nThis JSON file should contain the following data:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"author"))," - Your name."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"compatibility"))," - A list of Unmanic Plugin Handler versions that this Plugin is compatible with (see table below)."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"description"))," - A long description of your Plugin detailing what it does. To include line-breaks, insert a ",(0,a.kt)("inlineCode",{parentName:"li"},"\\n"),"."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"icon"))," - A URI to an image. If no icon is provided, the default Plugin icon will be used in the WebUI."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"id"))," - A string identifier for your Plugin. Should only contain lowercase ",(0,a.kt)("inlineCode",{parentName:"li"},"a-z")," or ",(0,a.kt)("inlineCode",{parentName:"li"},"_")," characters."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"name"))," - A very short name for your Plugin."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"platform"))," - Platform compatibility of this plugin. Can be any combination of ",'["all", "linux", "windows", "mac"]',"."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"priorities"))," - A dictionary of the initial priority that this Plugin's runners within the Plugin flow."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"tags"))," - A comma separated list able to be used as keywords when searching for Plugins."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("strong",{parentName:"li"},(0,a.kt)("inlineCode",{parentName:"strong"},"version"))," - The version of your Plugin.")),(0,a.kt)("h5",{id:"example"},"Example:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-json"},'{\n "author": "Josh.5",\n "compatibility": [\n 1,\n 2\n ],\n "description": "Specify a language tag for Unmanic to try and put as 1st subtitle track.",\n "icon": "https://raw.githubusercontent.com/Josh5/unmanic.plugin.reorder_subtitle_streams_by_language/master/icon.png",\n "id": "reorder_subtitle_streams_by_language",\n "name": "Re-order subtitle streams by language",\n "platform": [\n "all"\n ],\n "priorities": {\n "on_library_management_file_test": 99,\n "on_worker_process": 2\n },\n "tags": "subtitle,ffmpeg,library file test",\n "version": "1.0.4"\n}\n')),(0,a.kt)("h5",{id:"plugin-handler-compatibility"},"Plugin handler compatibility:"),(0,a.kt)("table",null,(0,a.kt)("thead",{parentName:"table"},(0,a.kt)("tr",{parentName:"thead"},(0,a.kt)("th",{parentName:"tr",align:null},"Plugin handler"),(0,a.kt)("th",{parentName:"tr",align:null},"Unmanic versions"))),(0,a.kt)("tbody",{parentName:"table"},(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"v1"),(0,a.kt)("td",{parentName:"tr",align:null},"v0.1.0 - v0.1.4")),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"v2"),(0,a.kt)("td",{parentName:"tr",align:null},"v0.2.0 - current")))),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"directory-lib-optional"},"Directory: ",(0,a.kt)("inlineCode",{parentName:"h3"},"lib")," ",(0,a.kt)("em",{parentName:"h3"},"(optional)")),(0,a.kt)("p",null,"Location of any additional Python modules imported by this plugin.\nUse this directory if you are including files from another project on GitHub, etc."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-packagejson-optional"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"package.json")," ",(0,a.kt)("em",{parentName:"h3"},"(optional)")),(0,a.kt)("p",null,"This file will be read during the plugin build in GitHub. Any nodeJS packages listed will be installed and packaged with the plugin."),(0,a.kt)("p",null,"Python modules will be installed to ",(0,a.kt)("inlineCode",{parentName:"p"},"{plugin root}/node_modules"),"."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-pluginpy"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"plugin.py")),(0,a.kt)("p",null,"The main Pugin Python module."),(0,a.kt)("p",null,"This file will be imported and it's functions called by the main Unmanic process."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"file-requirementstxt-optional"},"File: ",(0,a.kt)("inlineCode",{parentName:"h3"},"requirements.txt")," ",(0,a.kt)("em",{parentName:"h3"},"(optional)")),(0,a.kt)("p",null,"This file will be read during the plugin build in GitHub. Any Python modules listed will be installed and packaged with the plugin."),(0,a.kt)("p",null,"Python modules will be installed to ",(0,a.kt)("inlineCode",{parentName:"p"},"{plugin root}/site-packages"),"."),(0,a.kt)("hr",null),(0,a.kt)("h3",{id:"directory-static-optional"},"Directory: ",(0,a.kt)("inlineCode",{parentName:"h3"},"static")," ",(0,a.kt)("em",{parentName:"h3"},"(optional)")),(0,a.kt)("p",null,"Location of any static assets to be served via the webserver."),(0,a.kt)("p",null,"Assets located in this directory will be available via the webserver at ",(0,a.kt)("inlineCode",{parentName:"p"},"/unmanic/panel/{plugin ID}/static/(.*)"),"."),(0,a.kt)("h2",{id:"plugin-module"},"Plugin Module"),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"plugin.py")," file is a stand-alone Python module. From this module you may import other\nmodules as you see fit. There is no limitation on what may be executed within the\n",(0,a.kt)("a",{parentName:"p",href:"#runners"},(0,a.kt)("strong",{parentName:"a"},"runner")),"\nof your Plugin."),(0,a.kt)("hr",null),(0,a.kt)("h4",{id:"runners"},"Runners"),(0,a.kt)("p",null,"The Plugin module is made up of defined ",(0,a.kt)("strong",{parentName:"p"},"runner")," functions. A Plugin may include multiple\nrunner functions such that it is executed at multiple stages of the library optimisation process."),(0,a.kt)("p",null,"A ",(0,a.kt)("strong",{parentName:"p"},"data")," parameter is provided to the runner functions. This data parameter is a dictionary\nobject of information pertaining to that stage of the library optimisation process.\nThis data object's schema that is provided to a plugin runner should still exist once the plugin runner function has completed execution.\nDuring the function, that data may be manipulated as you see fit.\nA plugin runner may fail if data is removed from that data dictionary."),(0,a.kt)("p",null,"Plugins should be tested with the ",(0,a.kt)("a",{parentName:"p",href:"/docs/development/plugin_manager_cli"},(0,a.kt)("strong",{parentName:"a"},"Plugin Manager CLI"))," before publishing changes to ensure\nthat the returned data matches the required schema for all included runner functions."),(0,a.kt)("h5",{id:"example-1"},"Example:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-python"},"def on_worker_process(data):\n ...\n return\n")),(0,a.kt)("table",null,(0,a.kt)("thead",{parentName:"table"},(0,a.kt)("tr",{parentName:"thead"},(0,a.kt)("th",{parentName:"tr",align:null},"Runner Type"),(0,a.kt)("th",{parentName:"tr",align:null},"Documentation"))),(0,a.kt)("tbody",{parentName:"table"},(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"on_library_management_file_test"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#library-management---file-test"},"LINK"))),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"on_worker_process"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#worker---processing-file"},"LINK"))),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"on_postprocessor_file_movement"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#post-processor---file-movements"},"LINK"))),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"on_postprocessor_task_results"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#post-processor---marking-task-successfailure"},"LINK"))),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"render_frontend_panel"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#frontend---data-panel"},"LINK"))),(0,a.kt)("tr",{parentName:"tbody"},(0,a.kt)("td",{parentName:"tr",align:null},"render_plugin_api"),(0,a.kt)("td",{parentName:"tr",align:null},(0,a.kt)("a",{parentName:"td",href:"/docs/development/writing_plugins/plugin_runner_types#frontend---plugin-api"},"LINK"))))))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.f9e608e9.js b/assets/js/runtime~main.98e2da68.js similarity index 67% rename from assets/js/runtime~main.f9e608e9.js rename to assets/js/runtime~main.98e2da68.js index f791672..07f5dca 100644 --- a/assets/js/runtime~main.f9e608e9.js +++ b/assets/js/runtime~main.98e2da68.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,t,d,r,f={},c={};function o(e){var a=c[e];if(void 0!==a)return a.exports;var t=c[e]={id:e,loaded:!1,exports:{}};return f[e].call(t.exports,t,t.exports,o),t.loaded=!0,t.exports}o.m=f,o.c=c,e=[],o.O=(a,t,d,r)=>{if(!t){var f=1/0;for(i=0;i=r)&&Object.keys(o.O).every((e=>o.O[e](t[b])))?t.splice(b--,1):(c=!1,r0&&e[i-1][2]>r;i--)e[i]=e[i-1];e[i]=[t,d,r]},o.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return o.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(e,d){if(1&d&&(e=this(e)),8&d)return e;if("object"==typeof e&&e){if(4&d&&e.__esModule)return e;if(16&d&&"function"==typeof e.then)return e}var r=Object.create(null);o.r(r);var f={};a=a||[null,t({}),t([]),t(t)];for(var c=2&d&&e;"object"==typeof c&&!~a.indexOf(c);c=t(c))Object.getOwnPropertyNames(c).forEach((a=>f[a]=()=>e[a]));return f.default=()=>e,o.d(r,f),r},o.d=(e,a)=>{for(var t in a)o.o(a,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((a,t)=>(o.f[t](e,a),a)),[])),o.u=e=>"assets/js/"+({53:"935f2afb",391:"671b7a02",431:"2c43149e",845:"e88eb276",1179:"679adfa5",1339:"6b6121d2",1372:"1db64337",1479:"93c647df",2032:"5ed3387d",2258:"4272f3f7",2498:"27ea0f18",3071:"bf358533",3139:"bd2a989b",3210:"e06d0efa",3546:"03f53ba5",4183:"1472bf35",4195:"c4f5d8e4",4196:"10c9af81",4473:"dd1011bb",4560:"3b481376",4857:"4016f74c",5076:"97a0dc27",5276:"bdeda05a",5417:"659354ea",5816:"99baeeb1",5908:"4c1d2a83",6040:"1f1a83da",6137:"5e7328fa",6415:"3033f524",6488:"8fb0903d",6602:"238ca5be",6653:"f205d5b9",6826:"4dbd4719",7277:"4dfbd266",7362:"f1830286",7918:"17896441",7936:"958a8c59",8265:"564c64e3",8497:"2e4dc061",8662:"a7f03839",8670:"80733dee",9182:"67aa25ca",9514:"1be78505",9515:"2b377764",9576:"2f1b3cda"}[e]||e)+"."+{53:"3d879cc1",391:"13ada913",431:"9610cdb1",845:"77a6c2ad",1179:"9ab53412",1339:"5b635d4b",1372:"30ce1e19",1479:"beb83094",2004:"f2f27b34",2032:"5632ed4c",2258:"440bf7d0",2498:"b24861d2",3071:"e717c56f",3139:"2ba1b085",3210:"483a4bcb",3546:"309b9bec",4183:"a866362c",4195:"b6973c80",4196:"9600144e",4473:"28321d9e",4560:"80049986",4857:"e82f5e26",4972:"80fb9cdc",5076:"d40bb2de",5276:"7170630c",5417:"a3bc2ac4",5816:"26c4fc8b",5908:"890fd848",6040:"a98506d5",6066:"d845724c",6137:"c202562f",6415:"85bf5d70",6488:"3157c004",6602:"ae8f2060",6653:"db94e907",6826:"18e8ca65",7277:"cd978c8b",7362:"c9cbc996",7918:"c33c82b9",7936:"3634a64e",8265:"51a5105d",8497:"c575cec5",8662:"1e30a1a6",8670:"77464116",8746:"1a3f6323",9182:"dddfdbec",9506:"7eddef66",9514:"a0deb3b3",9515:"6fac9d49",9576:"81124715"}[e]+".js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),d={},r="unmanic-documentation:",o.l=(e,a,t,f)=>{if(d[e])d[e].push(a);else{var c,b;if(void 0!==t)for(var n=document.getElementsByTagName("script"),i=0;i{c.onerror=c.onload=null,clearTimeout(s);var r=d[e];if(delete d[e],c.parentNode&&c.parentNode.removeChild(c),r&&r.forEach((e=>e(t))),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=l.bind(null,c.onerror),c.onload=l.bind(null,c.onload),b&&document.head.appendChild(c)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.p="/",o.gca=function(e){return e={17896441:"7918","935f2afb":"53","671b7a02":"391","2c43149e":"431",e88eb276:"845","679adfa5":"1179","6b6121d2":"1339","1db64337":"1372","93c647df":"1479","5ed3387d":"2032","4272f3f7":"2258","27ea0f18":"2498",bf358533:"3071",bd2a989b:"3139",e06d0efa:"3210","03f53ba5":"3546","1472bf35":"4183",c4f5d8e4:"4195","10c9af81":"4196",dd1011bb:"4473","3b481376":"4560","4016f74c":"4857","97a0dc27":"5076",bdeda05a:"5276","659354ea":"5417","99baeeb1":"5816","4c1d2a83":"5908","1f1a83da":"6040","5e7328fa":"6137","3033f524":"6415","8fb0903d":"6488","238ca5be":"6602",f205d5b9:"6653","4dbd4719":"6826","4dfbd266":"7277",f1830286:"7362","958a8c59":"7936","564c64e3":"8265","2e4dc061":"8497",a7f03839:"8662","80733dee":"8670","67aa25ca":"9182","1be78505":"9514","2b377764":"9515","2f1b3cda":"9576"}[e]||e,o.p+o.u(e)},(()=>{var e={1303:0,532:0};o.f.j=(a,t)=>{var d=o.o(e,a)?e[a]:void 0;if(0!==d)if(d)t.push(d[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var r=new Promise(((t,r)=>d=e[a]=[t,r]));t.push(d[2]=r);var f=o.p+o.u(a),c=new Error;o.l(f,(t=>{if(o.o(e,a)&&(0!==(d=e[a])&&(e[a]=void 0),d)){var r=t&&("load"===t.type?"missing":t.type),f=t&&t.target&&t.target.src;c.message="Loading chunk "+a+" failed.\n("+r+": "+f+")",c.name="ChunkLoadError",c.type=r,c.request=f,d[1](c)}}),"chunk-"+a,a)}},o.O.j=a=>0===e[a];var a=(a,t)=>{var d,r,f=t[0],c=t[1],b=t[2],n=0;if(f.some((a=>0!==e[a]))){for(d in c)o.o(c,d)&&(o.m[d]=c[d]);if(b)var i=b(o)}for(a&&a(t);n{"use strict";var e,a,t,r,d,f={},c={};function o(e){var a=c[e];if(void 0!==a)return a.exports;var t=c[e]={id:e,loaded:!1,exports:{}};return f[e].call(t.exports,t,t.exports,o),t.loaded=!0,t.exports}o.m=f,o.c=c,e=[],o.O=(a,t,r,d)=>{if(!t){var f=1/0;for(i=0;i=d)&&Object.keys(o.O).every((e=>o.O[e](t[b])))?t.splice(b--,1):(c=!1,d0&&e[i-1][2]>d;i--)e[i]=e[i-1];e[i]=[t,r,d]},o.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return o.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var d=Object.create(null);o.r(d);var f={};a=a||[null,t({}),t([]),t(t)];for(var c=2&r&&e;"object"==typeof c&&!~a.indexOf(c);c=t(c))Object.getOwnPropertyNames(c).forEach((a=>f[a]=()=>e[a]));return f.default=()=>e,o.d(d,f),d},o.d=(e,a)=>{for(var t in a)o.o(a,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((a,t)=>(o.f[t](e,a),a)),[])),o.u=e=>"assets/js/"+({53:"935f2afb",391:"671b7a02",431:"2c43149e",845:"e88eb276",1179:"679adfa5",1339:"6b6121d2",1372:"1db64337",1479:"93c647df",2032:"5ed3387d",2258:"4272f3f7",2498:"27ea0f18",3071:"bf358533",3139:"bd2a989b",3210:"e06d0efa",3546:"03f53ba5",4183:"1472bf35",4195:"c4f5d8e4",4196:"10c9af81",4473:"dd1011bb",4560:"3b481376",4857:"4016f74c",5076:"97a0dc27",5276:"bdeda05a",5417:"659354ea",5816:"99baeeb1",5908:"4c1d2a83",6040:"1f1a83da",6137:"5e7328fa",6415:"3033f524",6488:"8fb0903d",6602:"238ca5be",6653:"f205d5b9",6826:"4dbd4719",7277:"4dfbd266",7362:"f1830286",7918:"17896441",7936:"958a8c59",8265:"564c64e3",8497:"2e4dc061",8662:"a7f03839",8670:"80733dee",9182:"67aa25ca",9514:"1be78505",9515:"2b377764",9576:"2f1b3cda"}[e]||e)+"."+{53:"3d879cc1",391:"13ada913",431:"9610cdb1",845:"77a6c2ad",1179:"9ab53412",1339:"5b635d4b",1372:"30ce1e19",1479:"beb83094",2004:"f2f27b34",2032:"5632ed4c",2258:"440bf7d0",2498:"b24861d2",3071:"e717c56f",3139:"2ba1b085",3210:"483a4bcb",3546:"309b9bec",4183:"a866362c",4195:"b6973c80",4196:"9600144e",4473:"28321d9e",4560:"80049986",4857:"e82f5e26",4972:"80fb9cdc",5076:"d40bb2de",5276:"7170630c",5417:"a3bc2ac4",5816:"26c4fc8b",5908:"890fd848",6040:"a98506d5",6066:"d845724c",6137:"c202562f",6415:"85bf5d70",6488:"2fb0c1b7",6602:"ae8f2060",6653:"db94e907",6826:"18e8ca65",7277:"cd978c8b",7362:"c9cbc996",7918:"c33c82b9",7936:"3634a64e",8265:"51a5105d",8497:"c575cec5",8662:"1e30a1a6",8670:"77464116",8746:"1a3f6323",9182:"dddfdbec",9506:"7eddef66",9514:"a0deb3b3",9515:"6fac9d49",9576:"81124715"}[e]+".js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),r={},d="unmanic-documentation:",o.l=(e,a,t,f)=>{if(r[e])r[e].push(a);else{var c,b;if(void 0!==t)for(var n=document.getElementsByTagName("script"),i=0;i{c.onerror=c.onload=null,clearTimeout(s);var d=r[e];if(delete r[e],c.parentNode&&c.parentNode.removeChild(c),d&&d.forEach((e=>e(t))),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=l.bind(null,c.onerror),c.onload=l.bind(null,c.onload),b&&document.head.appendChild(c)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.p="/",o.gca=function(e){return e={17896441:"7918","935f2afb":"53","671b7a02":"391","2c43149e":"431",e88eb276:"845","679adfa5":"1179","6b6121d2":"1339","1db64337":"1372","93c647df":"1479","5ed3387d":"2032","4272f3f7":"2258","27ea0f18":"2498",bf358533:"3071",bd2a989b:"3139",e06d0efa:"3210","03f53ba5":"3546","1472bf35":"4183",c4f5d8e4:"4195","10c9af81":"4196",dd1011bb:"4473","3b481376":"4560","4016f74c":"4857","97a0dc27":"5076",bdeda05a:"5276","659354ea":"5417","99baeeb1":"5816","4c1d2a83":"5908","1f1a83da":"6040","5e7328fa":"6137","3033f524":"6415","8fb0903d":"6488","238ca5be":"6602",f205d5b9:"6653","4dbd4719":"6826","4dfbd266":"7277",f1830286:"7362","958a8c59":"7936","564c64e3":"8265","2e4dc061":"8497",a7f03839:"8662","80733dee":"8670","67aa25ca":"9182","1be78505":"9514","2b377764":"9515","2f1b3cda":"9576"}[e]||e,o.p+o.u(e)},(()=>{var e={1303:0,532:0};o.f.j=(a,t)=>{var r=o.o(e,a)?e[a]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var d=new Promise(((t,d)=>r=e[a]=[t,d]));t.push(r[2]=d);var f=o.p+o.u(a),c=new Error;o.l(f,(t=>{if(o.o(e,a)&&(0!==(r=e[a])&&(e[a]=void 0),r)){var d=t&&("load"===t.type?"missing":t.type),f=t&&t.target&&t.target.src;c.message="Loading chunk "+a+" failed.\n("+d+": "+f+")",c.name="ChunkLoadError",c.type=d,c.request=f,r[1](c)}}),"chunk-"+a,a)}},o.O.j=a=>0===e[a];var a=(a,t)=>{var r,d,f=t[0],c=t[1],b=t[2],n=0;if(f.some((a=>0!==e[a]))){for(r in c)o.o(c,r)&&(o.m[r]=c[r]);if(b)var i=b(o)}for(a&&a(t);n Docker-Compose SMB/CIFS mounts | Unmanic - +

Docker-Compose SMB/CIFS mounts

Overview

Mounting a remote Windows share is possible with Docker, but requires a little more configuration.

  PUID=$(id -u)
PGID=$(id -g)

# CONFIG_DIR - Where you settings are saved
CONFIG_DIR=/config

# CACHE_DIR - A tmpfs or and folder for temporary conversion files
CACHE_DIR=/tmp/unmanic

# CIFS Mount params
REMOTE_IP=192.168.0.20
PATH_TO_LIBRARY=/library
USERNAME=user
PASSWORD=password

# First create the docker volume mounting the CIFS remote share
docker volume create \
--driver local \
--opt type=cifs \
--opt device=//${REMOTE_IP}${PATH_TO_LIBRARY} \
--opt o=username=${USERNAME},password=${PASSWORD},vers=3.0,uid=${PUID},gid=${PGID} \
--name cifs_mount

# Now create the docker container using the created CIFS volume
docker run -ti --rm \
-e PUID=${PUID} \
-e PGID=${PGID} \
-p 8888:8888 \
-v ${CONFIG_DIR}:/config \
-v cifs_mount:/library \
-v ${CACHE_DIR}:/tmp/unmanic \
josh5/unmanic:latest
- + \ No newline at end of file diff --git a/docs/advanced/hardware_accelerated_encoding_vaapi/index.html b/docs/advanced/hardware_accelerated_encoding_vaapi/index.html index 075ff92..fdb9b3c 100644 --- a/docs/advanced/hardware_accelerated_encoding_vaapi/index.html +++ b/docs/advanced/hardware_accelerated_encoding_vaapi/index.html @@ -4,7 +4,7 @@ Docker Hardware Acceleration - VAAPI | Unmanic - + @@ -12,7 +12,7 @@

Docker Hardware Acceleration - VAAPI

Overview

Unmanic supports hardware acceleration (HWA) of video decoding using FFmpeg. FFmpeg and Unmanic can support multiple hardware acceleration implementations such as nVidia NVENC and MediaCodec through Video Acceleration API's.

VAAPI is a Video Acceleration API that uses libva to interface with local drivers to provide HWA.

You can find a list of supported codecs for VAAPI here. Both Intel iGPU and AMD GPU can use VAAPI.

NOTE: AMD GPU requires open source driver Mesa 20.1 or higher to support hardware decoding HEVC.

Running Unmanic with support for VAAPI

To enable VAAPI, you will need to run Unmanic on a device that supports it.

If you intend to use Unmanic inside a Docker container, you will also need to pass through the required devices to the container.

An example of this is shown below:

  PUID=$(id -u)
PGID=$(id -g)

# CONFIG_DIR - Where you settings are saved
CONFIG_DIR=/config

# LIBRARY_DIR - The location/locations of your library
LIBRARY_DIR=/library

# CACHE_DIR - A tmpfs or and folder for temporary conversion files
CACHE_DIR=/tmp/unmanic

docker run -ti --rm \
-e PUID=${PUID} \
-e PGID=${PGID} \
--device=/dev/dri \
-p 8888:8888 \
-v ${CONFIG_DIR}:/config \
-v ${LIBRARY_DIR}:/library \
-v ${CACHE_DIR}:/tmp/unmanic \
josh5/unmanic:latest
- + \ No newline at end of file diff --git a/docs/configuration/libraries/adding_libraries/index.html b/docs/configuration/libraries/adding_libraries/index.html index 68a3e8d..dcc2e60 100644 --- a/docs/configuration/libraries/adding_libraries/index.html +++ b/docs/configuration/libraries/adding_libraries/index.html @@ -4,7 +4,7 @@ Adding Libraries | Unmanic - + @@ -13,7 +13,7 @@ All aspects of this default library may be modified, including its name, but the library itself can never be removed.

To add a new library, click the button under the libraries list.

Select the directory that you wish to use for the new library.

tip

You may add multiple libraries that point to the same path. This is commonly used for configuring custom plugin stacks to suit the needs of multiple file types within a directory.

A new library will be created with a blank configuration.

- + \ No newline at end of file diff --git a/docs/configuration/libraries/configure_libraries/index.html b/docs/configuration/libraries/configure_libraries/index.html index 6584c7e..b8dd504 100644 --- a/docs/configuration/libraries/configure_libraries/index.html +++ b/docs/configuration/libraries/configure_libraries/index.html @@ -4,7 +4,7 @@ Configuring Libraries | Unmanic - + @@ -14,7 +14,7 @@ devices with limited RAM availability.

Plugins

The library will do nothing on its own. In order to create any tasks for the files in a library, that library must first be configured with one or more plugins.

To add a new plugin, click the button.

Select a plugin from the list provided.

If the plugin you require is not yet installed, click the INSTALL PLUGIN button.

For more information, follow the instructions on installing plugins.

Plugin Flow

Once you have one or more plugins added to a library, you will be able to configure their flow.

Use the handle to drag the plugins into the order that you need them to be executed.

- + \ No newline at end of file diff --git a/docs/configuration/libraries/file_testing/index.html b/docs/configuration/libraries/file_testing/index.html index 67d5d71..b1c2562 100644 --- a/docs/configuration/libraries/file_testing/index.html +++ b/docs/configuration/libraries/file_testing/index.html @@ -4,7 +4,7 @@ Optimise File Testing | Unmanic - + @@ -16,7 +16,7 @@ option under the Library section in Unmanic's settings.

Increasing this value will spawn more threads to concurrently test the files. However, it will also use more CPU and RAM resources and on less powerful machines has more obvious diminishing returns.

It is recommended to initially set this value between 2 and 5 and increase it between library scans to determine the optimal value.

- + \ No newline at end of file diff --git a/docs/configuration/libraries/library_scanner/index.html b/docs/configuration/libraries/library_scanner/index.html index edebbb8..7a54c65 100644 --- a/docs/configuration/libraries/library_scanner/index.html +++ b/docs/configuration/libraries/library_scanner/index.html @@ -4,13 +4,13 @@ Library Scanning | Unmanic - +

Library Scanning

Unmanic is able to run a routine scan of your configured libraries to determine if files need tasks created for processing.

note

The scan will only run against a library if that library has the library scanner enabled.

Options

The options for the library scanner are configured under the Library section in Unmanic's settings.

Enable periodic library scans

Select this option if you wish for Unmanic to schedule a library scanner.

Library scan schedule in minutes

Set the time in minutes between scans of your files in the configured library path.

Run a one off library scan on startup

If selected, the library scan will be executed when Unmanic starts.

If selected, the library scan will follow symlinks.

- + \ No newline at end of file diff --git a/docs/configuration/libraries/managing_task_list/index.html b/docs/configuration/libraries/managing_task_list/index.html index 0e13120..1ee2c87 100644 --- a/docs/configuration/libraries/managing_task_list/index.html +++ b/docs/configuration/libraries/managing_task_list/index.html @@ -4,14 +4,14 @@ Managing the Task List | Unmanic - +

Managing the Task List

When Unmanic finds a file that needs processing, it will add it to the pending tasks list.

Under the Library section of Unmanic's settings, you have the option to configure if this task list is cleared each time Unmanic starts.

If selected, the Pending Tasks queue will be cleared whenever Unmanic starts.

tip

For larger libraries it is recommended to disable this option and preserve your Pending Tasks queue between Unmanic restarts.

- + \ No newline at end of file diff --git a/docs/configuration/link_settings/index.html b/docs/configuration/link_settings/index.html index 174f1f8..d3a97d4 100644 --- a/docs/configuration/link_settings/index.html +++ b/docs/configuration/link_settings/index.html @@ -4,7 +4,7 @@ Link Settings | Unmanic - + @@ -27,7 +27,7 @@ This is normal. Assume this value is plus or minus one.

tip

When wanting to maintain two libraries independently on the same PC, it is a good idea to link those installations and configure a Worker target to share amongst those installations.

This way, you get to maintain custom plugin stacks for each library, with the benefit of limiting the overall Worker count for that PC to whatever target you configure here.

- + \ No newline at end of file diff --git a/docs/configuration/plugins/adding_a_custom_plugin_repo/index.html b/docs/configuration/plugins/adding_a_custom_plugin_repo/index.html index 85af609..d5cd24f 100644 --- a/docs/configuration/plugins/adding_a_custom_plugin_repo/index.html +++ b/docs/configuration/plugins/adding_a_custom_plugin_repo/index.html @@ -4,13 +4,13 @@ Adding a Custom Plugin Repository | Unmanic - +

Adding a Custom Plugin Repository

Adding a repo

To add a custom repository located on GitHub, follow these steps:

  1. On the Plugins tab, click the ADD NEW button.
  1. Click the ADD REPOSITORY button.

  2. Add the maintainers URL into the New Repository text input.

  1. Click the SAVE button.

This will automatically refresh the available plugins to include the ones in the added repository.


Removing a repo

You can remove a custom repo by clicking on the REPOSITORIES LIST button and then selecting REMOVE.

- + \ No newline at end of file diff --git a/docs/configuration/plugins/configuring_plugins/index.html b/docs/configuration/plugins/configuring_plugins/index.html index ef713d9..f021eb9 100644 --- a/docs/configuration/plugins/configuring_plugins/index.html +++ b/docs/configuration/plugins/configuring_plugins/index.html @@ -4,7 +4,7 @@ Configuring Plugins | Unmanic - + @@ -18,7 +18,7 @@ configuration.

Click the button to open up the plugin's library configuration.

At the bottom you will have the option to reset the plugin's configuration back to the global configuration. This action will remove the library's override settings for this plugin.

- + \ No newline at end of file diff --git a/docs/configuration/plugins/installing_plugins/index.html b/docs/configuration/plugins/installing_plugins/index.html index 2fedd39..44bab5b 100644 --- a/docs/configuration/plugins/installing_plugins/index.html +++ b/docs/configuration/plugins/installing_plugins/index.html @@ -4,13 +4,13 @@ Installing Plugins | Unmanic - +

Installing Plugins

Plugins are hosted on repositories.

Unmanic comes configured to be able to download plugins from the official repository.

To install a plugin, follow these steps:

  1. On the Plugins tab, click the ADD NEW button.
  1. Click the REFRESH REPOSITORIES button.
  1. Install your desired plugin via the Install button.

To enable a plugin for a library, see Configuring Libraries

- + \ No newline at end of file diff --git a/docs/configuration/plugins/overview/index.html b/docs/configuration/plugins/overview/index.html index 0a3f3d6..210b6e1 100644 --- a/docs/configuration/plugins/overview/index.html +++ b/docs/configuration/plugins/overview/index.html @@ -4,7 +4,7 @@ Plugins Overview | Unmanic - + @@ -13,7 +13,7 @@ Plugins are stand-alone scripts or "modules" that are executed by Unmanic at defined stages during its task processing.

Usually, plugins are designed to carry out one function only. However, in order to carry out that one function, some plugins are designed to be executed at multiple stages of task processing.

For example, you may install a plugin that will run during the library scan process to detect files that need to be added to the pending tasks queue due to an incorrect video codec. When the worker picks up the task, it may execute that same plugin again which will now handle the job of transcoding the video streams to the configured codec.

In this example the plugin has carried out two separate jobs at different stages of Unmanic's task processing.

Use plugins to build powerful library optimisation flows.


Plugin execution during Task flow

Sections in pink will execute plugins configured for that stage of the process.

- + \ No newline at end of file diff --git a/docs/configuration/workers_settings/index.html b/docs/configuration/workers_settings/index.html index 43303e3..805fca6 100644 --- a/docs/configuration/workers_settings/index.html +++ b/docs/configuration/workers_settings/index.html @@ -4,7 +4,7 @@ Workers | Unmanic - + @@ -17,7 +17,7 @@ becomes idle, at which point it will be terminated.

Setting the event order

The configured worker events are executed from top to bottom. In some cases, you may wish for one event to come after another.

This can be achieved by dragging the items in the list of configured events into order that suites your needs.

Cache path

Set the location of the cache where task files will be temporarily stored while workers are carrying out jobs on them.

tip

For faster processing of I/O bound tasks, such as video encoding, try setting this to a tmpfs (RAM).

However, if you do this you will need to ensure that enough RAM is available to store the average file size times the number of workers that you are running.

- + \ No newline at end of file diff --git a/docs/dashboard/completed_tasks/index.html b/docs/dashboard/completed_tasks/index.html index 12d1b77..06d9e1c 100644 --- a/docs/dashboard/completed_tasks/index.html +++ b/docs/dashboard/completed_tasks/index.html @@ -4,7 +4,7 @@ Dashboard Completed Tasks | Unmanic - + @@ -13,7 +13,7 @@ or to delete the selected tasks from your completed tasks list altogether.

You may discover that some of your tasks fail and will be indicated by a red Failed status. You are able to view more details regarding the jobs carried out against your task by clicking on the button.

note

Tasks that are listed in the completed tasks list as Failed will be ignored in all future Library Scans or during detected Library File Monitor events.

Completed Task Details

The completed task details card will give you a full log of the commands that were run by every job executed by the Workers. You can use this log to diagnose issues that may be causing your tasks to fail.

- + \ No newline at end of file diff --git a/docs/dashboard/pending_tasks/index.html b/docs/dashboard/pending_tasks/index.html index 5e3b7e1..83ceeeb 100644 --- a/docs/dashboard/pending_tasks/index.html +++ b/docs/dashboard/pending_tasks/index.html @@ -4,13 +4,13 @@ Dashboard Pending Tasks | Unmanic - +

Dashboard Pending Tasks

What are Pending Tasks

A task is generated whenever a file is discovered that requires processing by one or more plugins.

The pending tasks are a list of file pointers that are currently awaiting to be picked up by the next available Worker.

Pending tasks are listed in order that they will be processed and limited to showing up to 10 tasks.

Managing Pending Tasks

You will see more details regarding your pending tasks queue if you expand the card by clicking the button in the top-right corner.

Once expanded you have the options to move a task to either the top or bottom of the list, or to remove the items from your pending tasks queue altogether.

From here you are also able to manually execute a scan of your library.

- + \ No newline at end of file diff --git a/docs/dashboard/workers/index.html b/docs/dashboard/workers/index.html index 9b7f949..da6677e 100644 --- a/docs/dashboard/workers/index.html +++ b/docs/dashboard/workers/index.html @@ -4,7 +4,7 @@ Dashboard Workers | Unmanic - + @@ -18,7 +18,7 @@ one is made available.

Processing - progress known

The worker is currently processing a task. The progress of the plugin's command is able to be parsed.

Processing - progress indeterminate

The worker is currently processing a task. The progress of the plugin's command is not able to be parsed.

Paused - idle

The worker is paused, and it currently has no task.

Paused - processing

The worker is paused. It does currently have a task assigned to it being processed.

Worker Status

You may expand each worker card display more in-depth information about its current status.

Once expanded you will see, among other things, a live stream of the current command's log output.

You will also have an Options dropdown for pausing/resuming or terminating this individual worker and its current subprocess.

- + \ No newline at end of file diff --git a/docs/development/developing_plugins/index.html b/docs/development/developing_plugins/index.html index 3ff1dbd..d30bea2 100644 --- a/docs/development/developing_plugins/index.html +++ b/docs/development/developing_plugins/index.html @@ -4,7 +4,7 @@ Plugin Development Overview | Unmanic - + @@ -20,7 +20,7 @@ optimisation process. However, it is best practice ensuring that a Plugin is designed to carry out only a single task.

Types of Plugins

The current list of supported types:

TypeStageDocumentation
File testLibrary ManagementLINK
Processing fileWorkerLINK
File movementsPost-processorLINK
Marking task success/failurePost-processorLINK

Plugins repositories

Unmanic hosts an official repository of plugins for Unmanic.

This is hosted on GitHub under the "official" branch.

Anyone is able to provide Pull Request to update existing plugins or add new plugins to this repository. Any additions will be vetted and tested to ensure they uphold the standard expected of official plugins.

If you wish to create your own repository to host or develop your own plugins, follow the guide here:

- + \ No newline at end of file diff --git a/docs/development/plugin_manager_cli/index.html b/docs/development/plugin_manager_cli/index.html index 201b548..2af4388 100644 --- a/docs/development/plugin_manager_cli/index.html +++ b/docs/development/plugin_manager_cli/index.html @@ -4,7 +4,7 @@ Plugin Manager CLI | Unmanic - + @@ -12,7 +12,7 @@

Plugin Manager CLI

Unmanic has a built-in Plugin Manager CLI. To access this, run:

unmanic --manage_plugins

This will not run the main Unmanic service and may be run while already running the Unmanic service without any interference.

You will be presented with the main menu

$ unmanic --manage_plugins
Starting migrations
There is nothing to migrate
UnmanicLogger - SETUP LOGGER
[?] What would you like to do?: List all installed plugins
> List all installed plugins
Test plugins
Create new plugin
Reload all plugins from disk
Remove plugin
Exit

From here you may select on of the available options.

OptionDescription
List installed pluginsLists all currently installed Plugins and their status within Unmanic's database
Test pluginsMenu. Lists all currently installed Plugins. Select one to test it
Create new pluginExecutes a guided process to generate a new Plugin template
Reload plugin from diskReload all Plugin data into the database and update any requirements
Remove pluginDelete the Plugin directory and all contents and remove the Plugin from the database
ExitExits the Plugin Manager CLI
- + \ No newline at end of file diff --git a/docs/development/plugin_repos/creating_a_pull_request/index.html b/docs/development/plugin_repos/creating_a_pull_request/index.html index 1c36d26..e2b312e 100644 --- a/docs/development/plugin_repos/creating_a_pull_request/index.html +++ b/docs/development/plugin_repos/creating_a_pull_request/index.html @@ -4,14 +4,14 @@ Creating a Pull Request to the Official Repository | Unmanic - +

Creating a Pull Request to the Official Repository

Submissions are encouraged for the plugin repository. However it is your responsibility to ensure that your plugin is compatible and meets all requirements prior to submission. Once a plugin is merged into the official repository, maintenance of that plugin will fall to the maintainer of the official repository.

If you wish to merge your plugin into the official repository, follow these steps


Opening PR

These instructions assume you have forked the official repository and wish to merge in your own plugin.

These instructions also assume that you have configured your locally cloned repo with a remote upstream pointing to the official repository.

git remote add upstream https://github.com/Unmanic/unmanic-plugins.git 
note

It is a good idea to set the variable $plugin_id before starting these commands

Eg:

plugin_id=path_ignore
plugin_version=0.0.1

Creating a New PR:

1) Pull latest changes from official branch

git checkout official
git pull upstream official

2) Create new PR branch from official

git checkout -b pr-${plugin_id}

3) Merge in the source files from your master branch

git read-tree --prefix=source/${plugin_id} -u master:source/${plugin_id}

4) Push to new branch and then open a PR

git commit -m "[${plugin_id}] ${plugin_version}"
git push -f origin pr-${plugin_id}

5) Open the PR

Open the PR from this new branch of your forked repo to the Unmanic plugins official branch.

Updating existing PR:

1) Pull latest changes from official branch

git checkout official
git pull upstream official

2) Checkout CLEAN PR branch from official

git branch -D pr-${plugin_id}
git checkout -b pr-${plugin_id}

3) Follow steps > 3 from Creating a New PR: above

Continue following steps 3+ from the New PR: steps above.

note

Take note of the force push in step 4. This is done so that we can squash the changes for the PR.

- + \ No newline at end of file diff --git a/docs/development/plugin_repos/creating_your_own_repo/index.html b/docs/development/plugin_repos/creating_your_own_repo/index.html index c67a2cb..1dd62d5 100644 --- a/docs/development/plugin_repos/creating_your_own_repo/index.html +++ b/docs/development/plugin_repos/creating_your_own_repo/index.html @@ -4,7 +4,7 @@ Creating Your Own Repository | Unmanic - + @@ -21,7 +21,7 @@ You also need to create a config.json file inside the root directory of your repo on the master branch. In this config.json file, add the following:

{
"id": "repository.YOUR_NAME_NO_SPACES",
"name": "THE NAME OF YOUR REPO",
"icon": ""
}

Once you have done this, commit the change and push it to the master branch.

You are now all set to start using your own Unmanic Plugin Repository. You can do this by Adding a Custom Repository in Unmanic's web interface.

- + \ No newline at end of file diff --git a/docs/development/writing_plugins/creating_a_new_plugin/index.html b/docs/development/writing_plugins/creating_a_new_plugin/index.html index 450cebd..08b3e6a 100644 --- a/docs/development/writing_plugins/creating_a_new_plugin/index.html +++ b/docs/development/writing_plugins/creating_a_new_plugin/index.html @@ -4,7 +4,7 @@ Creating a New Plugin | Unmanic - + @@ -15,7 +15,7 @@ and begin editing the files as necessary.

Be sure to test your Plugin with the Plugin Manager CLI as required.

tip

You can run the Plugin test functionality within the Plugin Manager CLI to print(data) or to test the functionality of your Plugin without needing to run the main Unmanic process.

- + \ No newline at end of file diff --git a/docs/development/writing_plugins/introduction/index.html b/docs/development/writing_plugins/introduction/index.html index 9a08181..63cbc21 100644 --- a/docs/development/writing_plugins/introduction/index.html +++ b/docs/development/writing_plugins/introduction/index.html @@ -4,7 +4,7 @@ Introduction to Writing Plugins | Unmanic - + @@ -12,7 +12,7 @@

Introduction to Writing Plugins

Writing Plugins for Unmanic should be easy.

All that is required to get started is a basic knowledge of writing Python scripts.

Directory Structure

The basic directory structure of a Plugin should be:

my_new_plugin
├── changelog.md
├── description.md
├── info.json
├── lib
├── package.json
├── plugin.py
├── requirements.txt
└── static

File: changelog.md

This file should be used to record changes made to the Plugin.


File: description.json (optional)

This file can be used to extend the description provided within the info.json file.


File: info.json

A JSON file containing the metadata of your Plugin. This JSON file should contain the following data:

  • author - Your name.
  • compatibility - A list of Unmanic Plugin Handler versions that this Plugin is compatible with (see table below).
  • description - A long description of your Plugin detailing what it does. To include line-breaks, insert a \n.
  • icon - A URI to an image. If no icon is provided, the default Plugin icon will be used in the WebUI.
  • id - A string identifier for your Plugin. Should only contain lowercase a-z or _ characters.
  • name - A very short name for your Plugin.
  • platform - Platform compatibility of this plugin. Can be any combination of ["all", "linux", "windows", "mac"].
  • priorities - A dictionary of the initial priority that this Plugin's runners within the Plugin flow.
  • tags - A comma separated list able to be used as keywords when searching for Plugins.
  • version - The version of your Plugin.
Example:
{
"author": "Josh.5",
"compatibility": [
1,
2
],
"description": "Specify a language tag for Unmanic to try and put as 1st subtitle track.",
"icon": "https://raw.githubusercontent.com/Josh5/unmanic.plugin.reorder_subtitle_streams_by_language/master/icon.png",
"id": "reorder_subtitle_streams_by_language",
"name": "Re-order subtitle streams by language",
"platform": [
"all"
],
"priorities": {
"on_library_management_file_test": 99,
"on_worker_process": 2
},
"tags": "subtitle,ffmpeg,library file test",
"version": "1.0.4"
}
Plugin handler compatibility:
Plugin handlerUnmanic versions
v1v0.1.0 - v0.1.4
v2v0.2.0 - current

Directory: lib (optional)

Location of any additional Python modules imported by this plugin. -Use this directory if you are including files from another project on GitHub, etc.


File: package.json (optional)

This file will be read during the plugin build in GitHub. Any nodeJS packages listed will be installed and packaged with the plugin.

Python modules will be installed to {plugin root}/node_modules.


File: plugin.py

The main Pugin Python module.

This file will be imported and it's functions called byt he main Unmanic process.


File: requirements.txt (optional)

This file will be read during the plugin build in GitHub. Any Python modules listed will be installed and packaged with the plugin.

Python modules will be installed to {plugin root}/site-packages.


Directory: static (optional)

Location of any static assets to be served via the webserver.

Assets located in this directory will be available via the webserver at /unmanic/panel/{plugin ID}/static/(.*).

Plugin Module

The plugin.py file is a stand-alone Python module. From this module you may import other +Use this directory if you are including files from another project on GitHub, etc.


File: package.json (optional)

This file will be read during the plugin build in GitHub. Any nodeJS packages listed will be installed and packaged with the plugin.

Python modules will be installed to {plugin root}/node_modules.


File: plugin.py

The main Pugin Python module.

This file will be imported and it's functions called by the main Unmanic process.


File: requirements.txt (optional)

This file will be read during the plugin build in GitHub. Any Python modules listed will be installed and packaged with the plugin.

Python modules will be installed to {plugin root}/site-packages.


Directory: static (optional)

Location of any static assets to be served via the webserver.

Assets located in this directory will be available via the webserver at /unmanic/panel/{plugin ID}/static/(.*).

Plugin Module

The plugin.py file is a stand-alone Python module. From this module you may import other modules as you see fit. There is no limitation on what may be executed within the runner of your Plugin.


Runners

The Plugin module is made up of defined runner functions. A Plugin may include multiple @@ -22,7 +22,7 @@ During the function, that data may be manipulated as you see fit. A plugin runner may fail if data is removed from that data dictionary.

Plugins should be tested with the Plugin Manager CLI before publishing changes to ensure that the returned data matches the required schema for all included runner functions.

Example:
def on_worker_process(data):
...
return
Runner TypeDocumentation
on_library_management_file_testLINK
on_worker_processLINK
on_postprocessor_file_movementLINK
on_postprocessor_task_resultsLINK
render_frontend_panelLINK
render_plugin_apiLINK
- + \ No newline at end of file diff --git a/docs/development/writing_plugins/plugin_runner_types/index.html b/docs/development/writing_plugins/plugin_runner_types/index.html index 87816b4..eb1fdd5 100644 --- a/docs/development/writing_plugins/plugin_runner_types/index.html +++ b/docs/development/writing_plugins/plugin_runner_types/index.html @@ -4,7 +4,7 @@ Plugin Runners | Unmanic - + @@ -16,7 +16,7 @@ The main Unmanic process will take the data dictionary returned from this runner and execute the command within it's own shell.


Post-processor - File movements

Executed:

Just prior to file copy operation from cached output file to source file's directory.

Function:

on_postprocessor_file_movement(data)

Provided data:
  • source_data [dictionary] - Data pertaining to the original source file.
  • remove_source_file [boolean] - Sets if Unmanic should remove the original source file after all copy operations are complete.
  • copy_file [boolean] - Sets if Unmanic should run a copy operation with the returned data variables.
  • file_in [string] - The converted cache file to be copied by the postprocessor.
  • file_out [string] - The destination file that the file will be copied to.
  • run_default_file_copy [boolean] - Whether Unmanic should perform the default file copy.
Example:
plugin.py

For an up-to-date example, see the Example Plugins Repo

def on_postprocessor_file_movement(data):
"""
Runner function - configures additional postprocessor file movements during the postprocessor stage of a task.

The 'data' object argument includes:
source_data - Dictionary containing data pertaining to the original source file ('abspath' and 'basename').
remove_source_file - Boolean, should Unmanic remove the original source file after all copy operations are complete.
copy_file - Boolean, should Unmanic run a copy operation with the returned data variables.
file_in - The converted cache file to be copied by the postprocessor.
file_out - The destination file that the file will be copied to.
run_default_file_copy - Whether Unmanic should perform the default file copy.

:param data:
:return:
"""

return
Details:

This Plugin runner should be used for executing additional file movements of a completed task.

note

This Plugin runner is only executed on a successfully completed task.


Post-processor - Marking task success/failure

Executed:

Just prior to the task cache directory cleanup.

Function:

on_postprocessor_task_results(data)

Provided data:
  • final_cache_path [string] - The path to the final cache file that was then used as the source for all destination files.
  • library_id [integer] - The library that the current task is associated with.
  • task_processing_success [boolean] - Specifies if all task processes complete successfully.
  • file_move_processes_success [boolean] - Specifies if all postprocessor movement tasks complete successfully.
  • destination_files [list] - All file paths created by postprocessor file movements.
  • source_data [dictionary] - Data pertaining to the original source file.
Example:
plugin.py

For an up-to-date example, see the Example Plugins Repo

def on_postprocessor_task_results(data):
"""
Runner function - provides a means for additional postprocessor functions based on the task success.

The 'data' object argument includes:
final_cache_path - The path to the final cache file that was then used as the source for all destination files.
library_id - The library that the current task is associated with.
task_processing_success - Boolean, did all task processes complete successfully.
file_move_processes_success - Boolean, did all postprocessor movement tasks complete successfully.
destination_files - List containing all file paths created by postprocessor file movements.
source_data - Dictionary containing data pertaining to the original source file.

:param data:
:return:
"""
return

Frontend - Data Panel

(since v0.1.0)

Executed:

From front-end URL

Function:

render_frontend_panel(data)

Provided data:
  • content_type [string] - The content type to be set when writing back to the browser.
  • content [string] - The content to print to the browser.
  • path [string] - The path received after the '/unmanic/panel' path.
  • arguments [dictionary] - A dictionary of GET arguments received.
Example:
plugin.py
def render_frontend_panel(data):
"""
Runner function - display a custom data panel in the frontend.

The 'data' object argument includes:
content_type - The content type to be set when writing back to the browser.
content - The content to print to the browser.
path - The path received after the '/unmanic/panel' path.
arguments - A dictionary of GET arguments received.

:param data:
:return:
"""

with open(os.path.abspath(os.path.join(os.path.dirname(__file__), 'static', 'index.html'))) as f:
content = f.read()
data['content'] = content.replace("{cache_buster}", str(uuid.uuid4()))

return
Details:

This Plugin runner type provides a Data Panel which will be displayed to the front-end.

Pages will be served from /unmanic/ui/data-panels?pluginId={plugin ID}.

Pages will be provided with a GET parameter either ?theme=light or ?theme=dark to assist with theme mapping with the main frontend.

Static assets stored in the plugins static directory will made available via the webserver at /unmanic/panel/{plugin ID}/static/(.*)


Frontend - Plugin API

(since v0.2.0)

Executed:

From front-end URL

Function:

render_plugin_api(data)

Provided data:
  • content_type [string] - The content type to be set when writing back to the browser.
  • content [string] - The content to print to the browser.
  • path [string] - The path received after the '/unmanic/panel' path.
  • uri [string] - The request uri.
  • query [string] - The request query.
  • arguments [dictionary] - A dictionary of GET arguments received.
  • body [dictionary] - A dictionary of body arguments received.
Example:
plugin.py
def render_plugin_api(data):
"""
Runner function - display a custom data panel in the frontend.

The 'data' object argument includes:
content_type - The content type to be set when writing back to the browser.
content - The content to print to the browser.
path - The path received after the '/unmanic/panel' path.
uri - The request uri.
query - The request query.
arguments - A dictionary of GET arguments received.
body - A dictionary of body arguments received.

:param data:
:return:
"""

# Store webhook content
settings = Settings()
profile_directory = settings.get_profile_directory()
time_now = time.time()
request_body = json.loads(data.get('body', '{}'))
with open(os.path.join(profile_directory, 'sonarr_webhook_{}.json'.format(time_now)), 'w') as outfile:
json.dump(request_body, outfile, indent=2)

return
Details:

This Plugin runner type provides an API endpoint which may be used to receive webhook requests or function as a REST API for the plugin.

Pages will be served from /unmanic/plugin_api/{plugin ID}.

- + \ No newline at end of file diff --git a/docs/development/writing_plugins/plugin_settings/index.html b/docs/development/writing_plugins/plugin_settings/index.html index 7a950fc..632c240 100644 --- a/docs/development/writing_plugins/plugin_settings/index.html +++ b/docs/development/writing_plugins/plugin_settings/index.html @@ -4,7 +4,7 @@ Accessing Plugin Settings | Unmanic - + @@ -31,7 +31,7 @@ you can do this by adding an __init__(self) method to your Settings class.

Inside this method, modify the form_settings object according what you need.

Example:
class Settings(PluginSettings):
settings = {
'Limit to specified extensions': False,
"Comma separated list of file extensions to process.": 'ts',
}

def __init__(self, *args, **kwargs):
super(Settings, self).__init__(*args, **kwargs)
self.form_settings = {
"Comma separated list of file extensions to process.": self.__set_allowed_extensions_form_settings(),
}

def __set_allowed_extensions_form_settings(self):
values = {}
if not self.get_setting('Limit to specified extensions'):
values["display"] = 'hidden'
return values

Overwriting labels

You may wish to have a long, descriptive label for your input field.

Adding a label entry to your form_settings dictionary will overwrite this value in the WebUI

Example:
class Settings(PluginSettings):
settings = {
"my_checkbox": False,
}
form_settings = {
"my_checkbox": {
"label": "Would you like to have this checkbox selected?",
},
}

Select drop-downs

You can use form input select elements if you configure an input_type as "select" entry to your form_settings dictionary. When you set the input_type as "select", you must also add a list of select_options.

Each select_options should consist of a dictionary containing a value and a label.

Example:
class Settings(PluginSettings):
settings = {
"NVENC Encoder Quality Preset": "medium",
}
form_settings = {
"NVENC Encoder Quality Preset": {
"input_type": "select",
"select_options": [
{
'value': "fast",
'label': "Fast",
},
{
'value': "medium",
'label': "Medium",
},
{
'value': "slow",
'label': "Slow",
},
{
'value': "lossless",
'label': "Lossless (slowest)",
},
],
},
}

Textarea

You may wish to allow multi-line input for your string variables.

By default, Unmanic's WebUI will treat a string input as a "text" input type. This will generate a <input type="text"> element.

You can override this in Unmanic's WebUI by setting the input_type as "textarea" in your form_settings dictionary.

Example:
class Settings(PluginSettings):
settings = {
"Patterns": "",
}

form_settings = {
"Patterns": {
"input_type": "textarea",
},
}

Select directory

(since v0.0.8)

If you intend to use a text input for specifying a path to a directory, you could use Unmanic's directory select browser popup rather than require the path to be entered manually.

This can be done by setting an input_type as "browse_directory" entry to your form_settings dictionary.

Example:
class Settings(PluginSettings):
settings = {
"Destination directory": "/library/complete",
}
form_settings = {
"Destination directory": {
"input_type": "browse_directory",
},
}

Slider

(since v0.1.0)

If your Plugin config option is to accept an integer and you have a set max/min value, you may wish to use the slider.

This can be done by setting an input_type as "slider" entry to your form_settings dictionary.

Example:
class Settings(PluginSettings):
settings = {
"bitrate": 2000,
}
form_settings = {
"bitrate": {
"label": "Bitrate",
"input_type": "slider",
"slider_options": {
"min": 1000,
"max": 10000,
"step": 100,
"suffix": "K"
},
},
}
- + \ No newline at end of file diff --git a/docs/development/writing_plugins/system_info/index.html b/docs/development/writing_plugins/system_info/index.html index 3058e46..4e6676d 100644 --- a/docs/development/writing_plugins/system_info/index.html +++ b/docs/development/writing_plugins/system_info/index.html @@ -4,7 +4,7 @@ Accessing System Info | Unmanic - + @@ -16,7 +16,7 @@ discretion of the Plugin's developer.

Example:
    from unmanic.libs.system import System

system = System()
system_info = system.info()

In this above example, the system_info variable will be filled with a dictionary of a range of system information.

This information my vary from system to system. See below for an example of what data may be returned.

System Information Example
{
"devices": {
"cpu_info": {
"python_version": "3.8.10.final.0 (64 bit)",
"cpuinfo_version": [
7,
0,
0
],
"cpuinfo_version_string": "7.0.0",
"arch": "X86_64",
"bits": 64,
"count": 8,
"arch_string_raw": "x86_64",
"vendor_id_raw": "GenuineIntel",
"brand_raw": "Intel(R) Core(TM) i7-6770HQ CPU @ 2.60GHz",
"hz_advertised_friendly": "2.6000 GHz",
"hz_actual_friendly": "2.8001 GHz",
"hz_advertised": [
2600000000,
0
],
"hz_actual": [
2800121000,
0
],
"stepping": 3,
"model": 94,
"family": 6,
"flags": [
"3dnowprefetch",
"abm",
"acpi",
"adx",
"aes",
"aperfmperf",
"apic",
"arat",
"arch_perfmon",
"art",
"avx",
"avx2",
"bmi1",
"bmi2",
"bts",
"clflush",
"clflushopt",
"cmov",
"constant_tsc",
"cpuid",
"cpuid_fault",
"cx16",
"cx8",
"de",
"ds_cpl",
"dtes64",
"dtherm",
"dts",
"epb",
"ept",
"ept_ad",
"erms",
"est",
"f16c",
"flexpriority",
"flush_l1d",
"fma",
"fpu",
"fsgsbase",
"fxsr",
"hle",
"ht",
"hwp",
"hwp_act_window",
"hwp_epp",
"hwp_notify",
"ibpb",
"ibrs",
"ida",
"intel_pt",
"invpcid",
"invpcid_single",
"lahf_lm",
"lm",
"mca",
"mce",
"md_clear",
"mmx",
"monitor",
"movbe",
"mpx",
"msr",
"mtrr",
"nonstop_tsc",
"nopl",
"nx",
"osxsave",
"pae",
"pat",
"pbe",
"pcid",
"pclmulqdq",
"pdcm",
"pdpe1gb",
"pebs",
"pge",
"pln",
"pni",
"popcnt",
"pse",
"pse36",
"pti",
"pts",
"rdrand",
"rdrnd",
"rdseed",
"rdtscp",
"rep_good",
"rtm",
"sdbg",
"sep",
"sgx",
"smap",
"smep",
"ss",
"ssbd",
"sse",
"sse2",
"sse4_1",
"sse4_2",
"ssse3",
"stibp",
"syscall",
"tm",
"tm2",
"tpr_shadow",
"tsc",
"tsc_adjust",
"tsc_deadline_timer",
"tscdeadline",
"vme",
"vmx",
"vnmi",
"vpid",
"x2apic",
"xgetbv1",
"xsave",
"xsavec",
"xsaveopt",
"xsaves",
"xtopology",
"xtpr"
],
"l3_cache_size": 6291456,
"l2_cache_size": "1 MiB",
"l1_data_cache_size": "128 KiB",
"l1_instruction_cache_size": "128 KiB",
"l2_cache_line_size": 256,
"l2_cache_associativity": 6
},
"gpu_info": []
},
"platform": [
"Linux",
"nightcrawler",
"5.8.0-44-generic",
"#50~20.04.1-Ubuntu SMP Wed Feb 10 21:07:30 UTC 2021",
"x86_64",
"x86_64"
],
"python": "3.8.5.final.0"
}
- + \ No newline at end of file diff --git a/docs/guides/filebot_post_processor/index.html b/docs/guides/filebot_post_processor/index.html index f0961aa..45547f5 100644 --- a/docs/guides/filebot_post_processor/index.html +++ b/docs/guides/filebot_post_processor/index.html @@ -4,7 +4,7 @@ Running a FileBot Post-processor | Unmanic - + @@ -13,7 +13,7 @@ Setup Time

Follow these instructions to configure the Unmanic Docker container for running FileBot as a Post-processor function.

Instructions:

1) Enable FileBot in the Unmanic container

The Unmanic container does not have FileBot installed out-of-the-box.

You will need to install it into the container during container startup.

  1. Create a file inside the container /config/startup.sh
  2. Inside this file append the following contents:

# _____ _ _ _ _
# | ___(_) | ___| |__ ___ | |_
# | |_ | | |/ _ \ '_ \ / _ \| __|
# | _| | | | __/ |_) | (_) | |_
# |_| |_|_|\___|_.__/ \___/ \__|
#
# Flag to easily disable installation on startup
INSTALL_FILEBOT=true
INSTALL_LEGACY_FILEBOT=false
# Define software versions (only used for legacy installation)
ARCH=amd64 # (amd64, 1386, armhf)
FILEBOT_VERSION=4.7.9

if [[ ${INSTALL_FILEBOT} == 'true' ]]; then
if ! command -v filebot &> /dev/null; then
# 1. Import signing keys
apt-get update
apt-get install -y --install-recommends dirmngr gnupg apt-transport-https
apt-key adv --fetch-keys "https://raw.githubusercontent.com/filebot/plugins/master/gpg/maintainer.pub"

# 2. Add deb repository to sources.list
echo "deb [arch=all] https://get.filebot.net/deb/ universal main" | tee /etc/apt/sources.list.d/filebot.list

# 3. Update package index
apt-get update

# 4. Install dependencies explicitly (otherwise apt-get autoremove may purge them)
apt-get install -y --install-recommends \
default-jre \
openjfx \
mediainfo \
libchromaprint-tools \
p7zip-full \
unrar

if [[ ${INSTALL_LEGACY_FILEBOT} == 'true' ]]; then
# 5. Install FileBot legacy version
mkdir -p /config/filebot/
pushd /config/filebot/
if [[ ! -e /config/filebot/filebot_${FILEBOT_VERSION}_${ARCH}.deb ]]; then
curl -kSL \
-o /config/filebot/filebot_${FILEBOT_VERSION}_${ARCH}.deb \
"https://cfhcable.dl.sourceforge.net/project/filebot/filebot/FileBot_${FILEBOT_VERSION}/filebot_${FILEBOT_VERSION}_${ARCH}.deb"
chmod a+rw /config/filebot/filebot_${FILEBOT_VERSION}_${ARCH}.deb
fi
# apt-get install -y openjdk-8-jdk-headless libjna-java
apt-get install -y /config/filebot/filebot_${FILEBOT_VERSION}_${ARCH}.deb

popd
else
# 5. Install FileBot from repo
apt-get install -y --install-recommends filebot
fi

fi
fi

note

It may take some time for this script to complete the installation of FileBot and all of its dependencies. During this time, Unmanic will not start, and the web UI will not be accessible. Be patient, it will only need to run this installation the first time the container is started.

tip

You can configure the script in the following ways:

VariableSet ValueDescription
INSTALL_FILEBOTtrue / falseEnable / Disable the script
INSTALL_LEGACY_FILEBOTtrueInstall the legacy version of FileBot link rather than the latest release
ARCHamd64 / 1386 / armhfSpecify the hardware (x86_64 / x86 / ARM) version of FileBot (only required when installing the legacy version)
FILEBOT_VERSION4.7.9Specify the version of FileBot to download (only required when installing the legacy version)
  1. Restart the container

During startup, this script will install FileBot if it is not already installed in this container.

2) Install the 'External Post-processor Script' Plugin

Open the plugin installer and install the plugin named 'External Post-processor Script'

3) Configure the 'External Post-processor Script' Plugin

Once installed, click on the 'External Post-processor Script' Plugin icon.

Click on Settings

4) Enable the 'External Post-processor Script' Plugin

In the Plugin table, enable the configured 'External Post-processor Script' Plugin


Plugin Config Examples

Here are some examples of what FileBot can be used for in an Unmanic Post-process...

References:

Rename TV Shows with the correct codec in the file name:

Run the command for each output file created by Unmanic

True

Command or script to execute:
filebot
Arguments to pass to the command or script:
--conflict skip
-non-strict
--db TheMovieDB::TV
-rename "{output_file_path}"
--format "{n.upperInitial().space('.')}-{s00e00}-{t.upperInitial().space('.')}-{source}-{vf}-{vc}"
- + \ No newline at end of file diff --git a/docs/guides/nvidia_support_unmanic_on_linux/index.html b/docs/guides/nvidia_support_unmanic_on_linux/index.html index 21eb41d..541ffc1 100644 --- a/docs/guides/nvidia_support_unmanic_on_linux/index.html +++ b/docs/guides/nvidia_support_unmanic_on_linux/index.html @@ -4,7 +4,7 @@ NVIDIA Hardware Acceleration on Linux | Unmanic - + @@ -14,7 +14,7 @@ The Docker image has FFmpeg pre-installed with support for NVENC/NVDEC

Install FFmpeg for your operating system.

It is recommend to use the Jellyfin FFmpeg builds, however any recent release of FFmpeg will work fine.

To ensure your FFmpeg installation is capable of running the NVENC encoders, run this command:

for i in encoders decoders filters; do echo $i:; ffmpeg -hide_banner -${i} | egrep -i "npp|cuvid|nvenc|cuda|nvdec"; done

You should see a list of available encoders and decoders.

4) Running in Docker with NVENC support

Installing the NVIDIA Container Toolkit

If you intend to use Unmanic inside a Docker container, you will also need to pass through the required devices to the container. With NVIDIA this is done by installing the nvidia-docker2 package on your host.

Once you have followed these steps, you can test that the Unmanic Docker container can use the NVENC hardware by running:

docker run --rm --gpus all --entrypoint="" josh5/unmanic nvidia-smi

You should see the following output:

Sun Apr 17 05:31:44 2022       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 510.54 Driver Version: 510.54 CUDA Version: 11.6 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 NVIDIA GeForce ... Off | 00000000:01:00.0 On | N/A |
| 0% 34C P8 N/A / 120W | 185MiB / 4096MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+

Creating the Docker container

  PUID=$(id -u)
PGID=$(id -g)

# CONFIG_DIR - Where you settings are saved
CONFIG_DIR=/config

# LIBRARY_DIR - The location/locations of your library
LIBRARY_DIR=/library

# CACHE_DIR - A tmpfs or and folder for temporary conversion files
CACHE_DIR=/tmp/unmanic

# NVIDIA_VISIBLE_DEVICES - The GPUs that will be accessible to the container
NVIDIA_VISIBLE_DEVICES=all

docker run -ti --rm \
-e PUID=${PUID} \
-e PGID=${PGID} \
-e NVIDIA_VISIBLE_DEVICES=${NVIDIA_VISIBLE_DEVICES} \
--runtime=nvidia \
-p 8888:8888 \
-v ${CONFIG_DIR}:/config \
-v ${LIBRARY_DIR}:/library \
-v ${CACHE_DIR}:/tmp/unmanic \
josh5/unmanic:latest
- + \ No newline at end of file diff --git a/docs/guides/script_subtitle_stripping/index.html b/docs/guides/script_subtitle_stripping/index.html index a3d506d..58095a3 100644 --- a/docs/guides/script_subtitle_stripping/index.html +++ b/docs/guides/script_subtitle_stripping/index.html @@ -4,7 +4,7 @@ Subtitle Stripping | Unmanic - + @@ -16,7 +16,7 @@ Therefore, it will not process files without another plugin in the library's plugin flow to test that the scanned files need to be processed.

tip

Compliment this configuration with the plugin Re-order subtitle streams by language.

You can re-order your subtitle streams to have a particular language first, then use this process to strip out all the ones remaining.

Instructions:

note

Once installed, you can configure the script by editing the top section:

###################################################################
# CONFIG:
# NUMBER_OF_SUB_STREAMS_TO_KEEP
# Specify the number of subtitle streams to keep.
# Script will preserve the number of subtitle streams specified
# in this value. All others will be stripped out.
# Set this value to 0 to remove all subtitle streams.
NUMBER_OF_SUB_STREAMS_TO_KEEP=1
###################################################################
Ensure you have installed the External Worker Processor Script plugin.

Configure the plugin with the following:

Execution Type:

Bash Script

Script:

#!/usr/bin/env bash
###
# File: strip-subs.sh
# Project: tmp.BXJ7lwK6lZ
# File Created: Friday, 14th October 2022 5:35:18 am
# Author: Josh Sunnex (jsunnex@gmail.com)
# -----
# Last Modified: Friday, 14th October 2022 7:17:32 am
# Modified By: Josh Sunnex (jsunnex@gmail.com)
###

###################################################################
# CONFIG:
# NUMBER_OF_SUB_STREAMS_TO_KEEP
# Specify the number of subtitle streams to keep.
# Script will preserve the number of subtitle streams specified
# in this value. All others will be stripped out.
# Set this value to 0 to remove all subtitle streams.
NUMBER_OF_SUB_STREAMS_TO_KEEP=1
###################################################################

# Parse Args
__library_id=""
__output_cache_file=""
__original_source_file=""
__original_source_size=""
__source_file=""
__source_size=""
__return_data_file=""
__positional_args=()
for i in "${@}"; do
case $i in
--library-id=*)
__library_id="${i#*=}"
shift # past argument=value
;;
--output-cache-file=*)
__output_cache_file="${i#*=}"
shift # past argument=value
;;
--original-source-file=*)
__original_source_file="${i#*=}"
shift # past argument=value
;;
--original-source-size=*)
__original_source_size="${i#*=}"
shift # past argument=value
;;
-s=*|--source-file=*)
__source_file="${i#*=}"
shift # past argument=value
;;
--source-size=*)
__source_size="${i#*=}"
shift # past argument=value
;;
--return-data-file=*)
__return_data_file="${i#*=}"
shift # past argument=value
;;
-*|--*)
echo "Unknown option $i"
exit 1
;;
*)
__positional_args+=("$1") # save positional arg
shift # past argument
;;
esac
done
set -- "${__positional_args[@]}" # restore positional parameters

# Helper functions
__jq_item() {
echo "${1}" | base64 --decode | jq -r "${2}"
}

# Generate ffmpeg command
echo
echo "Probing file '${__source_file}'..."
probe=$(ffprobe -show_format -show_streams -print_format json -loglevel quiet "${__source_file}")
if [[ $? -gt 0 ]]; then
# Probably not a video file
echo "Unable to probe file. This is probably not a valid video file so will ignore. EXIT!"
exit 0
fi
# Ensure no further pipes fail
set -eo pipefail

echo
echo "Building ffmpeg args..."
echo
args="-hide_banner -loglevel info -i '${__source_file}' -strict -2 -max_muxing_queue_size 10240 -map 0 -c:v copy -c:a copy"
subtitle_streams_count=0
found_things_to_do="false"
for stream in $(echo "${probe}" | jq -r '.streams[] | @base64'); do
index=$(__jq_item "${stream}" '.index')
codec_type=$(__jq_item "${stream}" '.codec_type')
codec_name=$(__jq_item "${stream}" '.codec_name')
echo " Stream #${index}: {type:${codec_type}, codec:${codec_name}}"
if [[ "${codec_type}" == "subtitle" ]]; then
sub_language=$(__jq_item "${stream}" '.tags.language')
sub_title=$(__jq_item "${stream}" '.tags.title')
echo " - Subtitile Info: {language:${sub_language}, title:${sub_title}}"
if [[ "${subtitle_streams_count}" -ge "${NUMBER_OF_SUB_STREAMS_TO_KEEP}" ]]; then
echo " - MARKING FOR REMOVAL"
args="${args} -map -0:s:${subtitle_streams_count}"
found_things_to_do="true"
else
args="${args} -c:s:${subtitle_streams_count} copy"
echo " - MARKING FOR COPY"
fi
((subtitle_streams_count+=1))
elif [[ "${codec_type}" == "video" ]]; then
vid_width=$(__jq_item "${stream}" '.width')
vid_height=$(__jq_item "${stream}" '.height')
echo " - Video Info: {width:${vid_width}, height:${vid_height}}"
echo " - MARKING FOR COPY"
elif [[ "${codec_type}" == "audio" ]]; then
audio_channels=$(__jq_item "${stream}" '.channels')
audio_bit_rate=$(__jq_item "${stream}" '.bit_rate')
echo " - Audio Info: {channels:${audio_channels}, bit_rate:${audio_bit_rate}}"
echo " - MARKING FOR COPY"
else
echo " - Stream Info: null"
fi
done
# Only provide a command to execute if streams were found to need modifying
if [[ "${found_things_to_do}" ]]; then
exec_command="ffmpeg ${args} -y '${__output_cache_file}'"
else
exec_command=""
fi
echo
echo "Command: ${exec_command}"

# Generate return object for Unmanic to execute the command
# This script could also execute the command from here if you wish, but Unmanic will process the log differently
return_object=$(jq --null-input \
--arg exec_command "${exec_command}" \
--arg file_out "${__output_cache_file}" \
'{"exec_command": $exec_command, "file_out": $file_out}')
if [[ ! -z ${__return_data_file} ]]; then
echo "${return_object}" > "${__return_data_file}"
cat "${__return_data_file}"
fi

Arguments:

--source-file="{source_file_path}" 
--output-cache-file="{file_out_path}"
--return-data-file="{data_json_file}"
- + \ No newline at end of file diff --git a/docs/guides/unmanic_kubernetes_install/index.html b/docs/guides/unmanic_kubernetes_install/index.html index 83f8244..e3496b0 100644 --- a/docs/guides/unmanic_kubernetes_install/index.html +++ b/docs/guides/unmanic_kubernetes_install/index.html @@ -4,7 +4,7 @@ Install Unmanic - Kubernetes | Unmanic - + @@ -13,7 +13,7 @@ Setup Time Original Author

Requirements

This requires a functional Kubernetes Cluster or similar (k3s, minikube, etc).

For Kubernetes installation instructions

note

K3S 1.22.7 was used to create this guide.

Running Unmanic

There are two parts to running Unmanic in Kubernetes.

  1. Deployment - The unmanic container configuration
  2. Service - The service that exposes the unmanic container to outside the Kubernetes cluster

For a basic deployment and service, create a file unmanic.yaml and append the following:

apiVersion: apps/v1
kind: Deployment
metadata:
name: unmanic-deployment
spec:
selector:
matchLabels:
app: unmanic
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: unmanic
spec:
containers:
- name: unmanic
image: josh5/unmanic:latest
ports:
- containerPort: 8888
name: unmanic-port
protocol: TCP
env:
- name: PUID
value: "<PUID>"
- name: PGID
value: "<PGID>"
volumeMounts:
- mountPath: /library
name: library
- mountPath: /config
name: unmanic-config
- mountPath: /tmp/unmanic
name: unmanic-cache
volumes:
- name: library
nfs:
server: <NFS_SERVER_ADDRESS>
path: </PATH/TO/NFS/SHARE>
- name: unmanic-config
emptyDir: {} # Please use a more permanent storage, see Tuning section
- name: unmanic-cache
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: unmanic-service
spec:
selector:
app: unmanic
type: NodePort
ports:
- name: unmanic-port
port: 8888
targetPort: 8888
nodePort: 30000

To start the deployment and service, run the following command: kubectl create -f unmanic.yaml

To delete the deployment and service, run the following command: kubectl delete -f unmanic.yaml

Tuning Kubernetes Unmanic Configuration

Note that the following values should be tuned based on need:

  • Using NFS to share the library path to Kubernetes, please update as necessary if using CIFS or another file sharing service
  • Using emptyDir for the config and cache path, please update emptyDir to more permanent paths
  • Using NodePort to expose the Unmanic service, please update when using a load balancer or other method to expose the unmanic deployment
note

It is important not to use emptyDir for the unmanic-config in a permanent installation because the config will be deleted upon stopping the deployment. Please consider using a more permanent volume such as iSCSI or backing up to NFS.

Accessing Unmanic via NodePort

Unmanic will be available via the web browser at following address: <Kubernetes_Cluster_Address>:30000

Linux Hardware Acceleration (VA-API)

Intel Integrated GPU

It is possible to expose underlying Intel integrated GPU hardware that supports QSV/VA-API hardware acceleration into a Kubernetes workload. The proper method for implementing this functionality is through two additional Kubernetes add-ons:

note

This guide does not walk through the full installation and configuration for the aforementioned Kubernetes add-ons. This process is relatively straightforward using the most up-to-date guides from the associated links to upstream repositories.

Prerequistes

  1. Validate hardware compatibility
  2. Install Intel GPU drivers
  3. Install and configure intel-gpu-plugin add-on
  4. Install and configure node-feature-discovery add-on
  5. Kubernetes manifest considerations

Validate Hardware Compatibility

The Kubernetes worker node exposing GPU functionality to workloads requires Intel GPU drivers to be installed at the host level. An official lookup table exists for GPU hardware values to confirm they are supported. In the following example, [8086:9a49] indicates the vendor 8086 (Intel) and 9a49 (GPU; Iris Xe) from the lookup table.

$ lspci -nn |grep  -Ei 'VGA|DISPLAY'
00:02.0 VGA compatible controller [0300]: Intel Corporation TigerLake-LP GT2 [Iris Xe Graphics] [8086:9a49] (rev 01)

Install Intel GPU drivers

The official Intel documentation outlines instructions for installation of the appropriate Intel drivers on Ubuntu 22.04 (jammy). Once the drivers have been installed, a reboot is required before validation steps below:

# Install hwinfo and vainfo
$ apt update && apt install -y hwinfo vainfo

# Confirm Intel GPU drivers are functional
$ hwinfo --display
25: PCI 02.0: 0300 VGA compatible controller (VGA)
[Created at pci.386]
Unique ID: _Znp.vvQ429ch_ZF
SysFS ID: /devices/pci0000:00/0000:00:02.0
SysFS BusID: 0000:00:02.0
Hardware Class: graphics card
Device Name: "Onboard - Video"
Model: "Intel UHD Graphics"
Vendor: pci 0x8086 "Intel Corporation"
Device: pci 0x9a49 "UHD Graphics"
SubVendor: pci 0x8086 "Intel Corporation"
SubDevice: pci 0x3002
Revision: 0x01
Driver: "i915"
Driver Modules: "i915"
Memory Range: 0x603c000000-0x603cffffff (rw,non-prefetchable)
Memory Range: 0x4000000000-0x400fffffff (ro,non-prefetchable)
I/O Ports: 0x3000-0x303f (rw)
Memory Range: 0x000c0000-0x000dffff (rw,non-prefetchable,disabled)
IRQ: 177 (15621845 events)
Module Alias: "pci:v00008086d00009A49sv00008086sd00003002bc03sc00i00"
Driver Info #0:
Driver Status: i915 is active
Driver Activation Cmd: "modprobe i915"
Config Status: cfg=new, avail=yes, need=no, active=unknown

Primary display adapter: #25

# Verify profiles supported by hardware
$ vainfo
libva info: VA-API version 1.16.0
libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so
libva info: Found init function __vaDriverInit_1_16
libva info: va_openDriver() returns 0
vainfo: VA-API version: 1.16 (libva 2.12.0)
vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics - 22.6.4 (aca8ee0)
vainfo: Supported profile and entrypoints
... truncated list of profiles ...

Install and Configure Intel GPU Plugin

The intel-gpu-plugin add-on will provide the ability to expose i915 devices to pods within the cluster. Due to the continually changing nature of Kubernetes, it's recommended to use the official documentation for installation of this add-on. This process will effectively passthrough the /dev/dri/* devices to workloads that are requesting it.

By default, passthrough will consume the associated device for that workload. It is possible to share a device with multiple workloads at the same time. Runtime argument -shared-dev-num 2 allows two workloads access to the GPU simultaneously. Additional information or runtime options can be found here.

note

It's important to note that exposing the GPU device is only one part of the equation. You also need to ensure any pods consuming the resources have adequate permissions to access the device. This can be achieved with addition of supplementalGroups: [44, 109] in the associated workload manifest. This is documented in the Supplemental Device Permissions section.

Install and Configure Node Feature Discovery (NFD)

The node-feature-discovery add-on is used to automatically label worker nodes with specialized hardware. Due to the continually changing nature of Kubernetes, it's recommended to use the official documentation for installation. Once deployed within a Kubernetes cluster, nodes containing Intel GPU hardware will be labeled with feature.node.kubernetes.io/custom-intel-gpu=true. You can use this label to apply affinity rules that will only schedule workloads on nodes with appropriate hardware.

Manifest Configuration Considerations

Additional configuration within Kubernetes is required to take advantage of the underlying hardware. The following sections will provide additional detail depending on your needs.

Resource Management

Providing resource management constraints will help to ensure a workload has adequate resources. Kubernetes will schedule workloads where the resources are available, allowing them to consume more than the requests values, but not beyond the limit value. In the following example, the workload is allowed access to the GPU device exposed through the Intel GPU plugin. Additionally, a maximum of 4 CPU cores and 8GB of memory can be consumed by the workload. The following YAML should be applied in the deployment.spec.template.spec section from the deployment manifest example.

      resources:
requests:
cpu: 100m
memory: 256M
gpu.intel.com/i915: 1
limits:
cpu: 4
memory: 8G
gpu.intel.com/i915: 1

Supplemental Device Permissions

Devices exposed by Intel GPU drivers in /dev/dri/* have special group permissions for video and render operating system groups.

$ ls -la /dev/dri
total 0
drwxr-xr-x 3 root root 100 Jan 21 23:15 .
drwxr-xr-x 20 root root 4840 Jan 23 09:15 ..
drwxr-xr-x 2 root root 80 Jan 21 23:15 by-path
crw-rw---- 1 root video 226, 0 Jan 21 23:15 card0
crw-rw---- 1 root render 226, 128 Jan 21 23:15 renderD128

$ egrep "video|render" /etc/group
video:x:44:
render:x:109:

In order to utilize the devices in a workload, you must ensure supplemental group permissions are applied. This can be achieved by adding a securityContext to the above deployment manifest. The following YAML should be applied in the deployment.spec.template.spec section from the deployment manifest example.

      securityContext:
supplementalGroups:
- 44
- 109

Affinity Rules

Affinity rules allow or disallow workloads from being placed where they shouldn't. For example, if only two worker nodes have compatible Intel GPU hardware, using the NFD node label feature.node.kubernetes.io/custom-intel-gpu will ensure workloads requiring GPU access to be scheduled on the corresponding nodes. The following YAML should be applied in the deployment.spec.template.spec section from the deployment manifest example.

      affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: feature.node.kubernetes.io/custom-intel-gpu
operator: In
values:
- "true"
- + \ No newline at end of file diff --git a/docs/guides/unmanic_link_installations/index.html b/docs/guides/unmanic_link_installations/index.html index 958f2e4..99b5bf4 100644 --- a/docs/guides/unmanic_link_installations/index.html +++ b/docs/guides/unmanic_link_installations/index.html @@ -4,7 +4,7 @@ Linking Unmanic Installations | Unmanic - + @@ -15,7 +15,7 @@ It is recommended to create a new library for this, instead of using the default library. :::

On main, use the plus sign to add a remote installation:

  • Enter the service address of remote using either the IP or hostname. For example, 192.168.1.3:8888`.
  • Accept None for authenication.
  • Click Add.
  • Click the Configure icon and enable Send tasks to this installation when workers are available.

Once setup, the once grey logo should now turn blue. This indicates the remote installation is connected. The final result should look like this (as of version 0.2.4):

Pending tasks on main should now be sent to remote. This may take a few minutes, depending on network speed.

:::Tip You can export the library configuration from main using the export button, copy the code shown in the panel that opens.

You can then use import on remote to paste the code.

:::

Route Tasks with Tags

At this point, this documentation makes the following assumptions about the existing setup thus far:

  • 2 installations of Unmanic exist on the network; main and remote.
  • Both installations are connected via Link.
  • Each installation has 1 library setup.
    • The library on main has either File Monitor, and/or Library scanner enabled (based upon the needs of the reader).
    • The library on remote has both File Monitor and Libary scanner disabled.

For the purposes of this exercise, our objective now is to ensure all work is sent exclusively to remote. In order to create this pipeline, proceed as follows:

  • Add a tag to the library on main. We'll use the string "work" with no quotes. Remember to hit the Enter key after typing the string or the tag won't persist.
  • Add the same tag to the library on remote.
  • Add the same tag to the worker on remote.
  • Ensure this tag is not present on remote's worker.

With this setup in place, the work flow operates as follows:

  • The library on main will be used to initiate all pending task (based on scans or file monitoring).
  • Each task will be sent to any linked worker with a matching tag.
  • Once the file associated with the task is tranferred to a valid worker, it will be processed according to the libary found to have a matching tag.
  • Once processing is complete, the file will be tranferred back to main.

In all cases where greater insight is needed (troubleshooting, etc), each Unmanic installation can have EnableDebugging toggled in the Logs section of the Help & Support page.

- + \ No newline at end of file diff --git a/docs/guides/unmanic_macos_install/index.html b/docs/guides/unmanic_macos_install/index.html index 6e62143..1bcf228 100644 --- a/docs/guides/unmanic_macos_install/index.html +++ b/docs/guides/unmanic_macos_install/index.html @@ -4,7 +4,7 @@ Install Unmanic - MacOS | Unmanic - + @@ -25,7 +25,7 @@ At the terminal, enter the following command

sudo su -

This will then prompt you for your account password. Enter your password and press enter.

Next, in the terminal, enter the following command

echo "python3 -m unmanic" >/usr/local/bin/start_unmanic.sh

Lastly, we will make the shell script executable by entering the following command in the terminal

chmod 755 /usr/local/bin/start_unmanic.sh

In the Terminal type exit to leave root access.

exit

6) Start Unmanic

From the Terminal, type

/usr/local/bin/start_unmanic.sh

This will start Unmanic. This terminal must be left open or Unmanic will stop.

7) Mount your media

Mount the remote drive that contains your media from the Go menu ->Connect to Server.

Enter the ip address and share name and click Connect.

When you are configuring Unmanic to access your library, it will now be locate under the /Volumes directory .

8) Configure Library path

Open a web brower of your choice and go to the following url

localhost:8888

Click the 3 horizontal lines in the upper left to expand the menu. Next go to Settings

In the setting menu, you will see your default library. Click the 3 horizontal bars to the right of the library to open the configuration.

Next, cliock on the box that says Library Path.

By default, Unmanic looks for a library at /library, but that is in use already in macOS.

Click the .. at the top of the window to go up a level.

Navigate to the /Volumes director and you should see the network drive that was mounted in the previous step.

From there, navigate to your media.

9) Optional Autostart

If you would like Unmanic to start automatically, we can compile an applescript applet to do this.

Open Applications->Utilities->Script Editor

Paste the following into the script editor

try
do shell script "/usr/local/bin/start_unmanic.sh"
on error errMsg
display dialog "ERROR: " & errMsg
end try

Next, go to File->Export

```

Export As: Start_Unmanic Where:Applications File Format: Application

Click the Save button

Close Script Editor.
Click the Apple Menu->System Preferences
Click Users & Groups
Click your username on the left and then click the Login Items tab.
Click the + and navigate to the Applications directory and click the Start_Unmanic application.
<img className={"screenshot"} src={require('/img/guides/unmanic_macos_install_images/login_items.png').default} />
- + \ No newline at end of file diff --git a/docs/guides/unmanic_windows_install/index.html b/docs/guides/unmanic_windows_install/index.html index 0bb984c..8e1dba8 100644 --- a/docs/guides/unmanic_windows_install/index.html +++ b/docs/guides/unmanic_windows_install/index.html @@ -4,7 +4,7 @@ Install Unmanic - Windows 10 (WSL + Docker) | Unmanic - + @@ -46,7 +46,7 @@ library, quit Docker by right clicking the Docker icon in the system tray and click Quit Docker Desktop. Docker must be quit completely, the Restart option will not resolve the issue.

Restart Docker Desktop and restart Unmanic from the Ubuntu bash window.

- + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index d6942d1..61ac97b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,14 +4,14 @@ Overview | Unmanic - +

Overview

DEMO

Unmanic is a simple tool for optimising your file library. You can use it to convert your files into a single, uniform format, manage file movements based on timestamps, or execute custom commands against a file based on its file size.

Unmanic provides you with the following main functions:

  • A built-in scheduler scans your whole library for files that do not conform to your configured file presets. Files found requiring processing are then queued.

  • A file/directory monitor. When a file is modified or a new file is added to your library, Unmanic can test the file against your configured presets. Like the first function, if this file requires processing, it is added to a queue for processing.

  • A handler to manage running multiple file manipulation tasks at a time.

  • A Web UI to easily configure, manage and monitor the progress of your library optimisation.

Simply configure Unmanic pointing it at your library and let it automatically manage that library for you.

- + \ No newline at end of file diff --git a/docs/installation/docker/index.html b/docs/installation/docker/index.html index 9b3a604..736a316 100644 --- a/docs/installation/docker/index.html +++ b/docs/installation/docker/index.html @@ -4,14 +4,14 @@ Installation via Docker | Unmanic - +

Installation via Docker

Supported Architectures

The Unmanic docker image is built for the following architectures:

note

It may work on others such as Windows or MacOS but they will not be officially supported at this time.

Follow the community provided Windows 10 installation guide if you wish to attempt installing and running Unmanic on Windows 10 or 11.

  • linux/amd64
  • linux/armv7
  • linux/arm64

Running Unmanic

There are a number of ways you may start Unmanic with Docker.

The most basic method is shown below:

  PUID=$(id -u)
PGID=$(id -g)

# CONFIG_DIR - Where you settings are saved
CONFIG_DIR=/config

# LIBRARY_DIR - The location/locations of your library
LIBRARY_DIR=/library

# CACHE_DIR - A tmpfs or and folder for temporary conversion files
CACHE_DIR=/tmp/unmanic

docker run -ti --rm \
-e PUID=${PUID} \
-e PGID=${PGID} \
-p 8888:8888 \
-v ${CONFIG_DIR}:/config \
-v ${LIBRARY_DIR}:/library \
-v ${CACHE_DIR}:/tmp/unmanic \
josh5/unmanic:latest

For more advanced methods using hardware acceleration, see these articles:

- + \ No newline at end of file diff --git a/docs/installation/pip/index.html b/docs/installation/pip/index.html index 325d2cd..754a8ef 100644 --- a/docs/installation/pip/index.html +++ b/docs/installation/pip/index.html @@ -4,13 +4,13 @@ Installation via Python pip | Unmanic - +

Installation via Python pip

Instructions

python3 -m pip install unmanic

Running Unmanic

To run unmanic, simply run:

unmanic

By default Unmanic's WebUI will be accessable on port 8888. You can change this by adding the --port flag.

For more information run:

unmanic --help

Automatically start Unmanic

You may wish to automatically start Unmanic when your system starts.

This example leverages systemd's userspace in order to be aligned with the install instructions documented above.

sudo mkdir -p /opt/unmanic
sudo chown $(id -u) /opt/unmanic
mkdir -p ~/.config/systemd/user

cat << EOF > ~/.config/systemd/user/unmanic.service
[Unit]
Description=Unmanic - Library Optimiser
After=network-online.target
StartLimitInterval=200
StartLimitBurst=3

[Service]
Type=simple
Environment="HOME_DIR=/opt/unmanic"
WorkingDirectory=/opt/unmanic
ExecStart=%h/.local/bin/unmanic
Restart=always
RestartSec=30

[Install]
WantedBy=default.target
EOF

systemctl --user enable unmanic.service
systemctl --user start unmanic.service

You can view the status of the running process

systemctl --user status unmanic.service

You can view the logs

journalctl --user -u unmanic.service
- + \ No newline at end of file diff --git a/docs/installation/synology/index.html b/docs/installation/synology/index.html index 311b2ce..786a60f 100644 --- a/docs/installation/synology/index.html +++ b/docs/installation/synology/index.html @@ -4,7 +4,7 @@ Installation on Synology | Unmanic - + @@ -12,7 +12,7 @@

Installation on Synology

Instructions:

  1. Configure the container general settings as pictured:
    • Enable high privilege
    • Resource limitation is optional
    • Auto-restart is optional
  1. Configure container volumes
    • The /video mount points to your video library which you want to be able to access in Unmanic
  1. Configure container ports
    • Local Port 8888 can be changed to whatever you prefer. Example: If set to "38888" the Web-UI will be accessible on port 38888 (http://server_ip:38888).
  1. Configure container environment
    • Set your PID and GID
    • Set your TZ
    • Add the LIBVA_DRIVER_NAME variable. Set to i965.

Finish setting up the container and run for the first time. Check if everything's running properly.

How to Find UID and GID on Synology without using SSH

  1. Go to Control Panel -> Task Scheduler -> Create -> Triggered Task -> User-defined script
  2. In General type the name of the task "id" in Task: field
  3. Check out "Enabled" checkbox. This task will only run manually when needed.

image

  1. In Task Settings type "id" (make sure to use only lowercase)

image

  1. Click Ok button.
  2. Go to Control Panel -> Task Scheduler -> Settings.
  3. Check "Save output results" and select a foleder where to save results in "Save To" field and click "Ok" button.

image

  1. Run "id" task.

image

  1. Open File Station, navigate to folder selected to save results, there should be a folder (name of the folder will match the task name) with a output.log file containing both UID and GID.

image

Optional:

  1. Go to Control Panel -> Task Scheduler -> Settings.
  2. Check out "Save output results" unless you want to continue to save task results.

Some optional configuration steps:

Adding HW-Acceleration capabilities:

Stop the container and export the settings to your local computer. Delete the container afterwards.

In the freshly downloaded .json file add the following text as shown in the picture below.

...

"devices": [{"CgroupPermissions": "rwm", "PathInContainer": "\/dev\/dri", "PathOnHost": "\/dev\/dri"}],

...

Import the modified .json file to your Synology NAS Docker Station and run the container.

Your Unmanic container should now be able to use HW-Acceleration with any supported Plugins.

- + \ No newline at end of file diff --git a/docs/installation/unraid/index.html b/docs/installation/unraid/index.html index 83d89f9..6cd3ed4 100644 --- a/docs/installation/unraid/index.html +++ b/docs/installation/unraid/index.html @@ -4,7 +4,7 @@ Installation on Unraid | Unmanic - + @@ -14,7 +14,7 @@ (changing 20g to the upper limit of whatever amount of RAM you wish to use):

--mount type=tmpfs,destination=/tmp/unmanic_tmpfs,tmpfs-size=20g

This does not reserve this amount of RAM for the container. It only creates a mount limited to the amount specified. You will still be limited to the amount of RAM available on the system as shared by all other processes.


Whatever option above that you choose, you will need ensure you have enough available memory for the container to use this location as the cache.

  1. Scroll to the bottom and click the "APPLY" button

Once the image is downloaded you should see it appear in the Docker tab.

Some optional configuration steps:

Nvidia GPU hardware encoding:

To use your NVIDIA GPU for hardware encoding in this container, follow these steps:

  1. Install the Unraid Nvidia-Driver Plugin to install an up-to-date NVIDIA driver onto your Unraid server.
  1. Toggle this Docker Container template editor to "Advanced View".
  2. In the "Extra Parameters" field, add "--runtime=nvidia".
  1. Expand the template "Show more settings..." section near the bottom.
  2. In the "NVIDIA_VISIBLE_DEVICES" variable, copy your GPU UUID (can be found in the Unraid Nvidia Plugin. See that forum thread for details)

Once you have completed these steps, you should be able to use the h264_nvenc and hvec_nvenc encoders.

Intel hardware encoding:

To use your INTEL Processor for hardware encoding in this container, follow these steps:

  1. Install the Intel-GPU-TOP Plugin.
  1. Toggle this Docker Container template editor to "Advanced View".
  2. In the "Extra Parameters" field, add "--device=/dev/dri".

Once you have completed these steps, you should be able to use the h264_vaapi and hevc_vaapi encoders.

AMD hardware encoding:

To use your AMD APU/GPU for hardware encoding in this container, follow these steps:

  1. Install the Radeon-TOP Plugin.
  1. Toggle this Docker Container template editor to "Advanced View".
  2. In the "Extra Parameters" field, add "--device=/dev/dri".

Once you have completed these steps, you should be able to use the h264_vaapi and hevc_vaapi encoders.

tip

Ryzen APU transcode performance is heavily determined by the speed of your RAM.

Setting RAM speed from 2400 to 3200 changed transcode times from around 10 minutes to around 5.

Also, if you have the option in your BIOS, set your iGPU RAM to at least 1GB (So long as you can afford to subtract that from your available RAM).

- + \ No newline at end of file diff --git a/docs/requirements/index.html b/docs/requirements/index.html index 203a8f7..bef692e 100644 --- a/docs/requirements/index.html +++ b/docs/requirements/index.html @@ -4,13 +4,13 @@ Requirements | Unmanic - +

Requirements

Software Requirements

Optional requires (based on configuration)

- + \ No newline at end of file diff --git a/index.html b/index.html index 30adc3e..388c12e 100644 --- a/index.html +++ b/index.html @@ -4,13 +4,13 @@ Optimise your file library Unmanic | Unmanic - +
Unmanic Logo

Unmanic gives you the power to automate the management of any file library through the use of customised modular task flows to suit your specific needs, giving you the ultimate, simple to configure, set-and-forget library optimisation tool.

Quick to configure

Quick to configure

Unmanic's basic features can be configured in under 2 minutes. For most people, this is all that is required.

Easy to Use

Easy to Use

Unmanic has been designed to be simple to install and easy to use.

Powerful for advanced users

Powerful for advanced users

Unmanic has been built with advanced features that allow more advanced users to dial in their library optimisation.

Multi-tasking

Multi-tasking

Unmanic can manage multiple file conversion tasks at a time.

Extensions

Extensions

Unmanic is fully extensible. You have the ability to write any Plugin to run any task.

Opensource

Opensource

Unmanic's code is released opensource under GPL v3.

- + \ No newline at end of file diff --git a/stats/index.html b/stats/index.html index 464e39e..8b35be4 100644 --- a/stats/index.html +++ b/stats/index.html @@ -4,13 +4,13 @@ Unmanic Stats | Unmanic - +
- + \ No newline at end of file