Browse Source

remove v2 ui and node (#1564)

pull/1565/head
Jason Dove 1 year ago committed by GitHub
parent
commit
15d4b0f82b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 20
      .github/workflows/artifacts.yml
  2. 22
      .github/workflows/vue-lint.yml
  3. 45
      ErsatzTV/ErsatzTV.csproj
  4. 45
      ErsatzTV/Startup.cs
  5. 2
      ErsatzTV/client-app/.env
  6. 28
      ErsatzTV/client-app/.gitignore
  7. 5
      ErsatzTV/client-app/.prettierrc
  8. 27
      ErsatzTV/client-app/README.md
  9. 3
      ErsatzTV/client-app/babel.config.js
  10. 19
      ErsatzTV/client-app/jsconfig.json
  11. 22229
      ErsatzTV/client-app/package-lock.json
  12. 80
      ErsatzTV/client-app/package.json
  13. BIN
      ErsatzTV/client-app/public/favicon.ico
  14. 17
      ErsatzTV/client-app/public/index.html
  15. 36
      ErsatzTV/client-app/src/App.vue
  16. 12
      ErsatzTV/client-app/src/assets/css/global.scss
  17. 88
      ErsatzTV/client-app/src/assets/images/ersatztv-logo.svg
  18. 77
      ErsatzTV/client-app/src/assets/images/ersatztv.svg
  19. 44
      ErsatzTV/client-app/src/components/Navigation/SideBarLanguageSelect.vue
  20. 27
      ErsatzTV/client-app/src/components/Navigation/SideBarLogo.vue
  21. 43
      ErsatzTV/client-app/src/components/Navigation/SideBarMenu.vue
  22. 37
      ErsatzTV/client-app/src/components/Navigation/SideBarMenuItem.vue
  23. 73
      ErsatzTV/client-app/src/components/Navigation/SideBarMenuItemExpandable.vue
  24. 28
      ErsatzTV/client-app/src/components/Navigation/SideBarVersion.vue
  25. 53
      ErsatzTV/client-app/src/components/Navigation/ToolBar.vue
  26. 96
      ErsatzTV/client-app/src/components/Navigation/ToolBarLinks.vue
  27. 18
      ErsatzTV/client-app/src/components/Navigation/ToolBarSearch.vue
  28. 35
      ErsatzTV/client-app/src/components/PopUps/SnackBar.vue
  29. 206
      ErsatzTV/client-app/src/locales/en.json
  30. 207
      ErsatzTV/client-app/src/locales/pt-br.json
  31. 37
      ErsatzTV/client-app/src/main.ts
  32. 18
      ErsatzTV/client-app/src/mixins/autoPageTitle.js
  33. 6
      ErsatzTV/client-app/src/models/Channel.ts
  34. 20
      ErsatzTV/client-app/src/models/FFmpegFullProfile.ts
  35. 7
      ErsatzTV/client-app/src/models/FFmpegProfile.ts
  36. 27
      ErsatzTV/client-app/src/plugins/i18n.ts
  37. 33
      ErsatzTV/client-app/src/plugins/vuetify.ts
  38. 252
      ErsatzTV/client-app/src/router/index.js
  39. 72
      ErsatzTV/client-app/src/services/AbstractApiService.ts
  40. 17
      ErsatzTV/client-app/src/services/ChannelService.ts
  41. 114
      ErsatzTV/client-app/src/services/FFmpegProfileService.ts
  42. 16
      ErsatzTV/client-app/src/services/VersionService.ts
  43. 11
      ErsatzTV/client-app/src/shims-tsx.d.ts
  44. 4
      ErsatzTV/client-app/src/shims-vue.d.ts
  45. 46
      ErsatzTV/client-app/src/stores/applicationState.js
  46. 28
      ErsatzTV/client-app/src/stores/languageState.js
  47. 27
      ErsatzTV/client-app/src/stores/snackbarState.js
  48. 909
      ErsatzTV/client-app/src/views/AddEditFFmpegProfilePage.vue
  49. 62
      ErsatzTV/client-app/src/views/ChannelsPage.vue
  50. 123
      ErsatzTV/client-app/src/views/FFmpegProfilesPage.vue
  51. 9
      ErsatzTV/client-app/src/views/HomePage.vue
  52. 44
      ErsatzTV/client-app/tsconfig.json
  53. 21
      ErsatzTV/client-app/vue.config.js
  54. 6
      docker/Dockerfile
  55. 5
      docker/arm32v7/Dockerfile
  56. 5
      docker/arm64/Dockerfile
  57. 5
      docker/nvidia/Dockerfile
  58. 5
      docker/vaapi/Dockerfile
  59. 2
      scripts/cleanup-all-code.sh
  60. 2
      scripts/cleanup-code.sh

20
.github/workflows/artifacts.yml

@ -51,16 +51,6 @@ jobs: @@ -51,16 +51,6 @@ jobs:
with:
dotnet-version: 8.0.x
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '14'
- name: Cache NPM dependencies
uses: bahmutov/npm-install@v1.8.28
with:
working-directory: ErsatzTV/client-app
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear
@ -177,22 +167,12 @@ jobs: @@ -177,22 +167,12 @@ jobs:
with:
dotnet-version: 8.0.x
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '14'
- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
if: ${{ matrix.kind == 'windows' }}
- name: Cache NPM dependencies
uses: bahmutov/npm-install@v1.8.28
with:
working-directory: ErsatzTV/client-app
- name: Clean
run: dotnet clean --configuration Release && dotnet nuget locals all --clear

22
.github/workflows/vue-lint.yml

@ -1,22 +0,0 @@ @@ -1,22 +0,0 @@
name: Lint VueJS Files on PR Request
on:
pull_request:
jobs:
vue-lint:
runs-on: ubuntu-latest
steps:
# Checkout the current repo
- name: Checkout current repository
uses: actions/checkout@v4
# Setup NodeJS version 16
- name: Setup NodeJS V16.x.x
uses: actions/setup-node@v3
with:
node-version: '16'
# CD into the current client directory and lint and build the client
- name: Lint and Build the client
run: |
cd ./ErsatzTV/client-app/
npm ci --no-optional
npm run lint
npm run build --if-present

45
ErsatzTV/ErsatzTV.csproj

@ -2,8 +2,6 @@ @@ -2,8 +2,6 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<SpaRoot>client-app\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
<IsPackable>false</IsPackable>
@ -17,43 +15,6 @@ @@ -17,43 +15,6 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- Build Target: Run webpack dist build -->
<Message Importance="high" Text="Running npm build..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="wwwroot\v2\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build">
<!-- Build Target: Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
</Target>
<Target Name="DebugEnsureNpm" AfterTargets="DebugEnsureNodeEnv">
<!-- Build Target: Ensure Node.js is installed -->
<Exec Command="npm --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
</Target>
<Target Name="EnsureNodeModulesInstalled" BeforeTargets="Build" Inputs="package.json" Outputs="packages-lock.json">
<!-- Build Target: Restore NPM packages using npm -->
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm ci" />
</Target>
<ItemGroup>
<PackageReference Include="Blazored.FluentValidation" Version="2.1.0" />
<PackageReference Include="Bugsnag.AspNet.Core" Version="3.1.0" />
@ -88,12 +49,6 @@ @@ -88,12 +49,6 @@
<PackageReference Include="VueCliMiddleware" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ErsatzTV.Application\ErsatzTV.Application.csproj" />
<ProjectReference Include="..\ErsatzTV.Core\ErsatzTV.Core.csproj" />

45
ErsatzTV/Startup.cs

@ -288,12 +288,6 @@ public class Startup @@ -288,12 +288,6 @@ public class Startup
services.AddFluentValidationAutoValidation();
services.AddValidatorsFromAssemblyContaining<Startup>();
string v2 = Environment.GetEnvironmentVariable("ETV_UI_V2");
if (!CurrentEnvironment.IsDevelopment() && !string.IsNullOrWhiteSpace(v2))
{
services.AddSpaStaticFiles(options => options.RootPath = "wwwroot/v2");
}
services.AddMemoryCache();
services.AddRazorPages(
@ -535,51 +529,12 @@ public class Startup @@ -535,51 +529,12 @@ public class Startup
app.UseAuthorization();
}
string v2 = Environment.GetEnvironmentVariable("ETV_UI_V2");
if (!env.IsDevelopment() && !string.IsNullOrWhiteSpace(v2))
{
app.Map(
"/v2",
app2 =>
{
if (string.IsNullOrWhiteSpace(env.WebRootPath))
{
env.WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
}
app2.UseRouting();
if (OidcHelper.IsEnabled)
{
app.UseAuthentication();
app.UseAuthorization();
}
app2.UseEndpoints(e => e.MapFallbackToFile("index.html"));
app2.UseFileServer(
new FileServerOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(env.WebRootPath, "v2"))
});
});
}
app.UseEndpoints(
endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
// if (env.IsDevelopment())
// {
// endpoints.MapToVueCliProxy(
// "/v2/{*path}",
// new SpaOptions { SourcePath = "client-app" },
// "serve",
// regex: "Compiled successfully",
// forceKill: true);
// }
});
}

2
ErsatzTV/client-app/.env

@ -1,2 +0,0 @@ @@ -1,2 +0,0 @@
VUE_APP_I18N_LOCALE=en
VUE_APP_I18N_FALLBACK_LOCALE=en

28
ErsatzTV/client-app/.gitignore vendored

@ -1,28 +0,0 @@ @@ -1,28 +0,0 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# generated files
src/assets/css/*.css
src/models/*.js
src/models/*.js.map

5
ErsatzTV/client-app/.prettierrc

@ -1,5 +0,0 @@ @@ -1,5 +0,0 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "none"
}

27
ErsatzTV/client-app/README.md

@ -1,27 +0,0 @@ @@ -1,27 +0,0 @@
# ErsatzTV Vuetify UI - WIP
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
To set a custom ErsatzTV api URL, use the following environment variable `VUE_APP_ETV_BASE_URL`.
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

3
ErsatzTV/client-app/babel.config.js

@ -1,3 +0,0 @@ @@ -1,3 +0,0 @@
module.exports = {
presets: ['@vue/cli-plugin-babel/preset']
};

19
ErsatzTV/client-app/jsconfig.json

@ -1,19 +0,0 @@ @@ -1,19 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

22229
ErsatzTV/client-app/package-lock.json generated

File diff suppressed because it is too large Load Diff

80
ErsatzTV/client-app/package.json

@ -1,80 +0,0 @@ @@ -1,80 +0,0 @@
{
"name": "client-app",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\""
},
"dependencies": {
"@mdi/font": "5.9.55",
"@sweetalert2/theme-dark": "^5.0.11",
"axios": "^0.26.1",
"core-js": "^3.8.3",
"pinia": "^2.0.11",
"pinia-plugin-persistedstate": "^1.5.1",
"roboto-fontface": "*",
"sweetalert2": "^11.4.17",
"vue": "^2.6.14",
"vue-class-component": "^7.2.6",
"vue-i18n": "^8.26.3",
"vue-property-decorator": "^9.1.2",
"vue-router": "^3.2.0",
"vue-sweetalert2": "^5.0.5",
"vuetify": "^2.6.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/composition-api": "^1.4.9",
"@vue/eslint-config-typescript": "^9.1.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3",
"sass": "~1.32.0",
"sass-loader": "^10.0.0",
"typescript": "~4.5.5",
"vue-cli-plugin-i18n": "~2.3.1",
"vue-cli-plugin-vuetify": "~2.4.7",
"vue-template-compiler": "^2.6.14",
"vuetify-loader": "^1.7.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"plugin:prettier/recommended",
"eslint:recommended",
"@vue/typescript"
],
"parserOptions": {
"parser": "@typescript-eslint/parser"
},
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto",
"tabWidth": 4
}
]
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

BIN
ErsatzTV/client-app/public/favicon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

17
ErsatzTV/client-app/public/index.html

@ -1,17 +0,0 @@ @@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

36
ErsatzTV/client-app/src/App.vue

@ -1,36 +0,0 @@ @@ -1,36 +0,0 @@
<template>
<v-app>
<Toolbar />
<!-- Sizes your content based upon application components -->
<v-main>
<!-- Provides the application the proper gutter -->
<v-container fluid>
<!-- If using vue-router -->
<router-view></router-view>
</v-container>
<SnackBar />
</v-main>
</v-app>
</template>
<script>
import Vue from 'vue';
import Toolbar from '@/components/Navigation/ToolBar.vue';
import SnackBar from '@/components/PopUps/SnackBar';
export default Vue.extend({
name: 'App',
components: { Toolbar, SnackBar },
data: () => ({})
});
</script>
<style>
main {
background-attachment: fixed;
background-repeat: no-repeat;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
background-position: center;
}
</style>

12
ErsatzTV/client-app/src/assets/css/global.scss

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
.swal2-container.swal2-center > .swal2-popup {
background-color: #1E1E1E !important;
border-color: green;
border-width: thin;
border-style: solid;
width: fit-content;
}
.swal2-html-container, .swal2-title {
color: white !important;
font-family:Cambria;
}

88
ErsatzTV/client-app/src/assets/images/ersatztv-logo.svg

@ -1,88 +0,0 @@ @@ -1,88 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="67.791206mm"
height="36.611721mm"
viewBox="0 0 67.791206 36.611721"
version="1.1"
id="svg8"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
sodipodi:docname="ersatztv-logo.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="199.98633"
inkscape:cy="92.251514"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="3408"
inkscape:window-height="1364"
inkscape:window-x="14"
inkscape:window-y="36"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-66.205082,-134.16488)">
<g
aria-label="Etv"
id="text78"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:50.8px;line-height:1.25;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;fill:#c0c000;fill-opacity:1;stroke:none;stroke-width:0.264583">
<path
d="m 66.205082,170.2805 v -36.11562 h 24.159765 v 6.02754 H 73.671293 v 8.60722 h 14.262695 v 5.8291 H 73.671293 v 9.64903 h 16.767968 v 6.00273 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:50.8px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#c0c000;fill-opacity:1;stroke-width:0.264583"
id="path98" />
<path
d="m 107.75293,170.10687 q -2.00918,0.66973 -4.31601,0.66973 -3.522269,0 -5.605863,-1.83555 -2.083593,-1.83555 -2.083593,-6.10195 v -14.16348 h -3.943946 v -5.23379 h 3.943946 v -6.62285 h 7.168556 v 6.62285 h 4.56406 v 5.23379 h -4.56406 v 13.41934 q 0,1.68671 0.69453,2.23242 0.69453,0.5457 2.05879,0.5457 1.0914,0 2.08359,-0.19844 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:50.8px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#00c0c0;fill-opacity:1;stroke-width:0.264583"
id="path100" />
<path
d="m 133.99629,143.44183 -9.05371,26.83867 h -6.82129 l -9.07852,-26.83867 h 7.51583 l 4.98574,18.03301 4.96093,-18.03301 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:50.8px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#00c000;fill-opacity:1;stroke-width:0.264583"
id="path102" />
</g>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="58.779785"
y="199.18695"
id="text82"><tspan
sodipodi:role="line"
id="tspan80"
x="58.779785"
y="199.18695"
style="stroke-width:0.264583"></tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

77
ErsatzTV/client-app/src/assets/images/ersatztv.svg

@ -1,77 +0,0 @@ @@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="191.74141mm"
height="34.2752mm"
viewBox="0 0 191.74141 34.2752"
version="1.1"
id="svg8"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
sodipodi:docname="ersatztv.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35"
inkscape:cx="368.16022"
inkscape:cy="84.671368"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="3436"
inkscape:window-height="1389"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-8.4242743,-125.76403)">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:19.8158px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.495396"
x="5.4054561"
y="159.5748"
id="text841"><tspan
sodipodi:role="line"
id="tspan839"
x="5.4054561"
y="159.5748"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:47.558px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.495396"><tspan
style="fill:#c0c000;fill-opacity:1"
id="tspan843">Ersatz</tspan><tspan
style="fill:#00c0c0;fill-opacity:1"
id="tspan845">T</tspan><tspan
style="fill:#00c000;fill-opacity:1"
id="tspan847">V</tspan></tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

44
ErsatzTV/client-app/src/components/Navigation/SideBarLanguageSelect.vue

@ -1,44 +0,0 @@ @@ -1,44 +0,0 @@
<template>
<v-select
v-on:change="setUserLanguage($event)"
:value="language"
dense
outlined
:items="langs"
:label="$t('sidebar.languages')"
class="ma-2"
></v-select>
</template>
<script>
import { mapState } from 'pinia';
import { languageState } from '@/stores/languageState';
export default {
name: 'language-changer',
data() {
return {
langs: [
{ text: this.$t('languages-code.en'), value: 'en' },
{ text: this.$t('languages-code.pt-br'), value: 'pt-br' }
]
};
},
computed: {
...mapState(languageState, ['language', 'setLanguage']),
languageCode: {
get() {
return languageState.language;
},
set(value) {
this.setLanguage(value);
}
}
},
methods: {
setUserLanguage: ($event) => {
languageState().setLanguage($event);
}
}
};
</script>

27
ErsatzTV/client-app/src/components/Navigation/SideBarLogo.vue

@ -1,27 +0,0 @@ @@ -1,27 +0,0 @@
<template>
<span>
<v-img
v-if="!isNavigationMini"
src="@/assets/images/ersatztv.svg"
class="ma-3"
></v-img>
<v-img
v-if="isNavigationMini"
src="@/assets/images/ersatztv-logo.svg"
class="ma-1"
></v-img>
<v-divider inset></v-divider>
</span>
</template>
<script>
import { mapState } from 'pinia';
import { applicationState } from '@/stores/applicationState';
export default {
name: 'SideBarLogo',
computed: {
...mapState(applicationState, ['isNavigationMini'])
}
};
</script>

43
ErsatzTV/client-app/src/components/Navigation/SideBarMenu.vue

@ -1,43 +0,0 @@ @@ -1,43 +0,0 @@
<template>
<v-list nav dense>
<span v-for="(nav, i) in navigation" :key="i">
<SideBarMenuItem
v-if="(!nav.children || !nav.showchildren) && !nav.meta.hidden"
:name="nav.name"
:path="nav.path"
:icon="nav.meta.icon"
:disabled="nav.meta.disabled"
/>
<SideBarMenuItemExpandable
v-else-if="nav.children"
@click.native="disableMiniNavigation()"
:name="nav.name"
:icon="nav.meta.icon"
:disabled="nav.meta.disabled"
:children="nav.children"
/>
</span>
</v-list>
</template>
<script>
import SideBarMenuItem from './SideBarMenuItem';
import SideBarMenuItemExpandable from './SideBarMenuItemExpandable';
import { mapState } from 'pinia';
import { applicationState } from '@/stores/applicationState';
export default {
name: 'NavSidebar',
components: { SideBarMenuItem, SideBarMenuItemExpandable },
data: () => ({
navigation: null
}),
computed: {
...mapState(applicationState, ['disableMiniNavigation'])
},
beforeMount: function () {
//Pull in navigation from routes and load into DOM
this.navigation = this.$router.options.routes;
}
};
</script>

37
ErsatzTV/client-app/src/components/Navigation/SideBarMenuItem.vue

@ -1,37 +0,0 @@ @@ -1,37 +0,0 @@
<template>
<v-list-item-group color="primary">
<v-list-item :to="path" :disabled="disabled">
<v-list-item-icon>
<v-icon v-text="icon" :disabled="disabled" />
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="$t(name)" />
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</template>
<script>
export default {
name: 'SideBarMenuItem',
props: {
name: {
type: String,
required: true
},
path: {
type: String,
required: true
},
icon: {
type: String,
required: true
},
disabled: {
type: Boolean,
required: true
}
}
};
</script>

73
ErsatzTV/client-app/src/components/Navigation/SideBarMenuItemExpandable.vue

@ -1,73 +0,0 @@ @@ -1,73 +0,0 @@
<template>
<v-list-group
color="primary"
v-model="menuItemOpened"
:disabled="disabled"
:prepend-icon="icon"
no-action
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title v-text="$t(name)"></v-list-item-title>
</v-list-item-content>
</template>
<v-list-item
v-for="child in children"
:key="child.name"
:to="child.path"
:disabled="child.meta.disabled"
>
<v-list-item-icon>
<v-icon
v-text="child.meta.icon"
:disabled="child.meta.disabled"
/>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="$t(child.name)"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-group>
</template>
<script>
export default {
name: 'SideBarMenuItemExpandable',
props: {
name: {
type: String,
required: true
},
icon: {
type: String,
required: true
},
disabled: {
type: Boolean,
required: true
},
children: {
type: Array,
required: true
},
nonmenuchildren: {
type: Array,
required: false
}
},
data: () => ({
opened: false
}),
computed: {
menuItemOpened: {
get: function () {
return this.opened;
},
set: function () {
return !this.opened;
}
}
}
};
</script>

28
ErsatzTV/client-app/src/components/Navigation/SideBarVersion.vue

@ -1,28 +0,0 @@ @@ -1,28 +0,0 @@
<template>
<span v-if="!isNavigationMini" class="text-center">
<v-divider inset></v-divider>
<h4 class="pt-2">ErsatzTV {{ $t('sidebar.version') }}</h4>
<p>{{ currentServerVersion }}</p>
</span>
</template>
<script>
import { mapState } from 'pinia';
import { applicationState } from '@/stores/applicationState';
export default {
name: 'SideBarVersion',
setup() {
applicationState().getVersion();
},
computed: {
...mapState(applicationState, [
'currentServerVersion',
'isNavigationMini'
])
}
};
</script>
<style scoped></style>

53
ErsatzTV/client-app/src/components/Navigation/ToolBar.vue

@ -1,53 +0,0 @@ @@ -1,53 +0,0 @@
<template>
<nav>
<v-app-bar flat app dense absolute>
<v-app-bar-nav-icon @click.stop="toggleMiniNavigation()" />
<ToolBarSearch />
<v-spacer />
<ToolBarLinks />
</v-app-bar>
<v-navigation-drawer app :mini-variant="isNavigationMini" permanent>
<template v-slot:prepend>
<SideBarLogo />
</template>
<SideBarMenu />
<template v-slot:append>
<SideBarLanguageSelect />
<SideBarVersion />
</template>
</v-navigation-drawer>
</nav>
</template>
<script>
import SideBarLogo from './SideBarLogo.vue';
import SideBarMenu from './SideBarMenu.vue';
import SideBarVersion from './SideBarVersion';
import ToolBarLinks from './ToolBarLinks';
import ToolBarSearch from './ToolBarSearch';
import { mapState } from 'pinia';
import { applicationState } from '@/stores/applicationState';
import SideBarLanguageSelect from './SideBarLanguageSelect.vue';
export default {
name: 'NavToolbar',
components: {
SideBarMenu,
SideBarLogo,
SideBarVersion,
ToolBarLinks,
ToolBarSearch,
SideBarLanguageSelect
},
computed: {
...mapState(applicationState, [
'isNavigationMini',
'toggleMiniNavigation'
])
}
};
</script>

96
ErsatzTV/client-app/src/components/Navigation/ToolBarLinks.vue

@ -1,96 +0,0 @@ @@ -1,96 +0,0 @@
<template>
<span>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-btn
class="ma-2"
outlined
color="primary"
@click="copyTextToClipboard(navBarURLs.m3uURL)"
v-bind="attrs"
v-on="on"
small
>
<v-icon>mdi-playlist-play</v-icon> M3U
</v-btn>
</template>
<span>{{ $t('tool-bar.click-to-copy') }}</span>
</v-tooltip>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-btn
class="ma-2"
outlined
color="primary"
@click="copyTextToClipboard(navBarURLs.xmlURL)"
v-bind="attrs"
v-on="on"
small
>
<v-icon>mdi-xml</v-icon> XML
</v-btn>
</template>
<span>{{ $t('tool-bar.click-to-copy') }}</span>
</v-tooltip>
<v-btn
icon
color="secondary"
:href="navBarURLs.documentationURL"
target="_blank"
>
<v-icon>mdi-file-document</v-icon>
</v-btn>
<v-btn
icon
color="secondary"
:href="navBarURLs.discordURL"
target="_blank"
>
<v-icon>mdi-discord</v-icon>
</v-btn>
<v-btn
icon
color="secondary"
:href="navBarURLs.githubURL"
target="_blank"
>
<v-icon>mdi-github</v-icon>
</v-btn>
</span>
</template>
<script>
import { mapState } from 'pinia';
import { applicationState } from '@/stores/applicationState';
import { snackbarState } from '@/stores/snackbarState';
export default {
name: 'ToolBarLinks.vue',
data: () => ({
toast: false
}),
computed: {
...mapState(applicationState, ['navBarURLs']),
...mapState(snackbarState, ['showSnackbar'])
},
methods: {
copyTextToClipboard(text) {
try {
navigator.clipboard.writeText(text);
this.showSnackbar(this.$t('tool-bar.copy-success'));
} catch (error) {
console.error(error);
this.showSnackbar(
this.$t('tool-bar.copy-failure', { message: error })
);
}
}
}
};
</script>
<style scoped></style>

18
ErsatzTV/client-app/src/components/Navigation/ToolBarSearch.vue

@ -1,18 +0,0 @@ @@ -1,18 +0,0 @@
<template>
<v-text-field
outlined
class="ml-5"
:label="$t('tool-bar.search')"
prepend-inner-icon="mdi-magnify"
hide-details
dense
></v-text-field>
</template>
<script>
export default {
name: 'ToolBarSearch'
};
</script>
<style scoped></style>

35
ErsatzTV/client-app/src/components/PopUps/SnackBar.vue

@ -1,35 +0,0 @@ @@ -1,35 +0,0 @@
<template>
<v-snackbar v-model="snackbar" :timeout="timeout">
{{ currentMessage }}
<v-btn text color="primary" @click.native="closeSnackbar()"
>Close</v-btn
>
</v-snackbar>
</template>
<script>
import { mapState } from 'pinia';
import { snackbarState } from '@/stores/snackbarState';
export default {
data: () => ({
timeout: 4000
}),
computed: {
...mapState(snackbarState, [
'currentMessage',
'isVisible',
'closeSnackbar',
'openSnackbar'
]),
snackbar: {
get() {
return this.isVisible;
},
set() {
this.closeSnackbar();
}
}
}
};
</script>

206
ErsatzTV/client-app/src/locales/en.json

@ -1,206 +0,0 @@ @@ -1,206 +0,0 @@
{
"$vuetify": {
"badge": "Badge",
"close": "Close",
"dataIterator": {
"noResultsText": "No matching records found",
"loadingText": "Loading items..."
},
"dataTable": {
"itemsPerPageText": "Rows per page:",
"ariaLabel": {
"sortDescending": "Sorted descending.",
"sortAscending": "Sorted ascending.",
"sortNone": "Not sorted.",
"activateNone": "Activate to remove sorting.",
"activateDescending": "Activate to sort descending.",
"activateAscending": "Activate to sort ascending."
},
"sortBy": "Sort by"
},
"dataFooter": {
"itemsPerPageText": "Items per page:",
"itemsPerPageAll": "All",
"nextPage": "Next page",
"prevPage": "Previous page",
"firstPage": "First page",
"lastPage": "Last page",
"pageText": "{0}-{1} of {2}"
},
"datePicker": {
"itemsSelected": "{0} selected",
"nextMonthAriaLabel": "Next month",
"nextYearAriaLabel": "Next year",
"prevMonthAriaLabel": "Previous month",
"prevYearAriaLabel": "Previous year"
},
"noDataText": "No data available",
"carousel": {
"prev": "Previous visual",
"next": "Next visual",
"ariaLabel": {
"delimiter": "Carousel slide {0} of {1}"
}
},
"calendar": {
"moreEvents": "{0} more"
},
"fileInput": {
"counter": "{0} files",
"counterSize": "{0} files ({1} in total)"
},
"timePicker": {
"am": "AM",
"pm": "PM"
},
"pagination": {
"ariaLabel": {
"wrapper": "Pagination Navigation",
"next": "Next page",
"previous": "Previous page",
"page": "Goto Page {0}",
"currentPage": "Current Page, Page {0}"
}
},
"rating": {
"ariaLabel": {
"icon": "Rating {0} of {1}"
}
}
},
"languages-code": {
"en": "English",
"pt-br": "Portuguese (Brazil)"
},
"tool-bar": {
"search": "Search...",
"click-to-copy": "Click to copy to clipboard!",
"copy-success": "Successfully copied text to clipboard!",
"copy-failure": "Failed to copy text to clipboard! Error: {message}"
},
"sidebar": {
"languages": "Languages",
"version": "Version"
},
"home": {
"title": "Home"
},
"ffmpeg-profiles": {
"title": "FFmpeg Profiles",
"add-profile": "Add FFmpeg Profile",
"actions": "Actions",
"delete-dialog-title": "Are you sure?",
"delete-dialog-text": "Delete {profileName} FFmpeg Profile?",
"no": "No",
"yes": "Yes",
"profile-deleted": "FFmpeg Profile deleted!",
"table": {
"name": "Name",
"resolution": "Resolution",
"video": "Video",
"audio": "Audio"
}
},
"edit-ffmpeg-profile": {
"General": "General",
"Video": "Video",
"Audio": "Audio",
"Name": "Name",
"thread-count": "Thread Count",
"preferred-resolution": "Preferred Resolution",
"format": "Format",
"bitrate": "Bitrate",
"buffer-size": "Buffer Size",
"hardware-acceleration": "Hardware Acceleration",
"vaapi-driver": "VAAPI Driver",
"vaapi-device": "VAAPI Device",
"normalize-framerate": "Normalize Framerate",
"auto-deinterlace-video": "Auto Deinterlace Video",
"channels": "Channels",
"sample-rate": "Sample Rate",
"normalize-loudness": "Normalize Loudness",
"save-profile": "Save Profile",
"cancel": "Cancel",
"help": "Help",
"add-profile": "Add FFmpeg Profile",
"edit-profile": "Edit FFmpeg Profile",
"profile-saved": "FFmpeg Profile Saved!"
},
"watermarks": {
"title": "Watermarks"
},
"media-sources": {
"title": "Media Sources",
"local": {
"title": "Local"
},
"emby": {
"title": "Emby"
},
"jellyfin": {
"title": "Jellyfin"
},
"plex": {
"title": "Plex"
}
},
"lists": {
"title": "Lists",
"collections": {
"title": "Collections"
},
"trakt-lists": {
"title": "Trakt Lists"
},
"filler-presets": {
"title": "Filler Presets"
}
},
"schedules": {
"title": "Schedules"
},
"playouts": {
"title": "Playouts"
},
"settings": {
"title": "Settings"
},
"logs": {
"title": "Logs"
},
"media": {
"title": "Media",
"libraries": {
"title": "Libraries"
},
"trash": {
"title": "Trash"
},
"tv-shows": {
"title": "TV Shows"
},
"movies": {
"title": "Movies"
},
"music-videos": {
"title": "Music Videos"
},
"other-videos": {
"title": "Other Videos"
},
"songs": {
"title": "Songs"
}
},
"channels": {
"title": "Channels",
"table": {
"number": "Number",
"logo": "Logo",
"name": "Name",
"language": "Language",
"mode": "Mode",
"ffmpeg-profile": "FFmpeg Profile"
}
}
}

207
ErsatzTV/client-app/src/locales/pt-br.json

@ -1,207 +0,0 @@ @@ -1,207 +0,0 @@
{
"$vuetify": {
"badge": "Distintivo",
"close": "Fechar",
"Actions": "Ações",
"dataIterator": {
"noResultsText": "Nenhum dado encontrado",
"loadingText": "Carregando itens..."
},
"dataTable": {
"itemsPerPageText": "Linhas por página:",
"ariaLabel": {
"sortDescending": "Ordenado decrescente.",
"sortAscending": "Ordenado crescente.",
"sortNone": "Não ordenado.",
"activateNone": "Ative para remover a ordenação.",
"activateDescending": "Ative para ordenar decrescente.",
"activateAscending": "Ative para ordenar crescente."
},
"sortBy": "Ordenar por"
},
"dataFooter": {
"itemsPerPageText": "Itens por página:",
"itemsPerPageAll": "Todos",
"nextPage": "Próxima página",
"prevPage": "Página anterior",
"firstPage": "Primeira página",
"lastPage": "Última página",
"pageText": "{0}-{1} de {2}"
},
"datePicker": {
"itemsSelected": "{0} selecionado(s)",
"nextMonthAriaLabel": "Próximo mês",
"nextYearAriaLabel": "Próximo ano",
"prevMonthAriaLabel": "Mês anterior",
"prevYearAriaLabel": "Ano anterior"
},
"noDataText": "Não há dados disponíveis",
"carousel": {
"prev": "Visão anterior",
"next": "Próxima visão",
"ariaLabel": {
"delimiter": "Slide {0} de {1} do carrossel"
}
},
"calendar": {
"moreEvents": "Mais {0}"
},
"fileInput": {
"counter": "{0} arquivo(s)",
"counterSize": "{0} arquivo(s) ({1} no total)"
},
"timePicker": {
"am": "AM",
"pm": "PM"
},
"pagination": {
"ariaLabel": {
"wrapper": "Navegação de paginação",
"next": "Próxima página",
"previous": "Página anterior",
"page": "Ir à página {0}",
"currentPage": "Página atual, página {0}"
}
},
"rating": {
"ariaLabel": {
"icon": "Rating {0} de {1}"
}
}
},
"languages-code": {
"en": "Inglês",
"pt-br": "Português (Brasil)"
},
"tool-bar": {
"search": "Busca...",
"click-to-copy": "Clique para copiar para a área de transferência!",
"copy-success": "Texto copiado com sucesso para a área de transferência!",
"copy-failure": "Falha ao copiar o texto para a área de transferência! Erro: {message}"
},
"sidebar": {
"languages": "Línguas",
"version": "Versão"
},
"home": {
"title": "Início"
},
"ffmpeg-profiles": {
"title": "Perfis FFmpeg",
"add-profile": "Adicionar perfil FFmpeg",
"actions": "Ações",
"delete-dialog-title": "Tem certeza?",
"delete-dialog-text": "Excluir perfil {profileName} do FFmpeg?",
"no": "Não",
"yes": "Sim",
"profile-deleted": "Perfil do FFmpeg excluído!",
"table": {
"name": "Nome",
"resolution": "Resolução",
"video": "Vídeo",
"audio": "Áudio"
}
},
"edit-ffmpeg-profile": {
"General": "Em geral",
"Video": "Vídeo",
"Audio": "Áudio",
"Name": "Nome",
"thread-count": "Contagem de fios",
"preferred-resolution": "Resolução preferencial",
"format": "Formato",
"bitrate": "Taxa de bits",
"buffer-size": "Tamanho do buffer",
"hardware-acceleration": "Aceleraçao do hardware",
"vaapi-driver": "Driver VAAPI",
"vaapi-device": "Dispositivo VAAPI",
"normalize-framerate": "Normalizar taxa de quadros",
"auto-deinterlace-video": "Vídeo de desentrelaçamento automático",
"channels": "Canais",
"sample-rate": "Taxa de amostragem",
"normalize-loudness": "Normalizar volume",
"save-profile": "Salvar perfil",
"cancel": "Cancelar",
"help": "Ajuda",
"add-profile": "Adicionar perfil FFmpeg",
"edit-profile": "Editar perfil do FFmpeg",
"profile-saved": "Perfil do FFmpeg salvo!"
},
"watermarks": {
"title": "Marcas d'água"
},
"media-sources": {
"title": "Fontes de mídia",
"local": {
"title": "Local"
},
"emby": {
"title": "Emby"
},
"jellyfin": {
"title": "Jellyfin"
},
"plex": {
"title": "Plex"
}
},
"lists": {
"title": "Listas",
"collections": {
"title": "Coleções"
},
"trakt-lists": {
"title": "Listas Trakt"
},
"filler-presets": {
"title": "Predefinições de preenchimento"
}
},
"schedules": {
"title": "Horários"
},
"playouts": {
"title": "Playouts"
},
"settings": {
"title": "Definições"
},
"logs": {
"title": "Logs"
},
"media": {
"title": "Mídias",
"libraries": {
"title": "Bibliotecas"
},
"trash": {
"title": "Lixo"
},
"tv-shows": {
"title": "Séries"
},
"movies": {
"title": "Filmes"
},
"music-videos": {
"title": "Vídeos de música"
},
"other-videos": {
"title": "Other Videos"
},
"songs": {
"title": "Songs"
}
},
"channels": {
"title": "Canais",
"table": {
"number": "Numero",
"logo": "Logo",
"name": "Nome",
"language": "Idioma",
"mode": "Modo",
"ffmpeg-profile": "Perfil FFmpeg"
}
}
}

37
ErsatzTV/client-app/src/main.ts

@ -1,37 +0,0 @@ @@ -1,37 +0,0 @@
import Vue from 'vue';
import App from './App.vue';
import vuetify from './plugins/vuetify';
import router from './router';
import { createPinia, PiniaVuePlugin } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import autoPageTitleMixin from './mixins/autoPageTitle';
import 'roboto-fontface/css/roboto/roboto-fontface.css';
import '@mdi/font/css/materialdesignicons.css';
import i18n from './plugins/i18n';
import VueSweetalert2 from 'vue-sweetalert2';
import 'sweetalert2/dist/sweetalert2.min.css';
import './assets/css/global.scss';
Vue.config.productionTip = false;
Vue.use(PiniaVuePlugin);
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
// Mixin to automate the page title when navigating... Will default to "ErsatzTV if no title value exported from page.
Vue.mixin(autoPageTitleMixin);
const options = {
confirmButtonColor: '#1E1E1E',
cancelButtonColor: '#1E1E1E',
background: '#1E1E1E',
iconColor: '#4CAF50'
};
Vue.use(VueSweetalert2, options);
new Vue({
vuetify,
router,
pinia,
i18n,
render: (h) => h(App)
}).$mount('#app');

18
ErsatzTV/client-app/src/mixins/autoPageTitle.js

@ -1,18 +0,0 @@ @@ -1,18 +0,0 @@
function getTitle(vm) {
const { title } = vm.$options;
if (title) {
return typeof title === 'function' ? title.call(vm) : title;
}
if (vm._data.title) {
return vm._data.title;
}
}
export default {
created() {
const title = getTitle(this);
if (title) {
document.title = `ErsatzTV | ${title}`;
}
}
};

6
ErsatzTV/client-app/src/models/Channel.ts

@ -1,6 +0,0 @@ @@ -1,6 +0,0 @@
export interface Channel {
id: number;
number: string;
name: string;
streamingMode: string;
}

20
ErsatzTV/client-app/src/models/FFmpegFullProfile.ts

@ -1,20 +0,0 @@ @@ -1,20 +0,0 @@
export interface FFmpegFullProfile {
Id: number;
name: string;
threadCount: number;
hardwareAcceleration: number;
vaapiDriver: number;
vaapiDevice: string;
resolutionId: number;
videoFormat: number;
videoBitrate: number;
videoBufferSize: number;
audioFormat: number;
audioBitrate: number;
audioBufferSize: number;
normalizeLoudness: boolean;
audioChannels: number;
audioSampleRate: number;
normalizeFramerate: boolean;
deinterlaceVideo: boolean;
}

7
ErsatzTV/client-app/src/models/FFmpegProfile.ts

@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
export interface FFmpegProfile {
id: number;
name: string;
resolution: string;
video: string;
audio: string;
}

27
ErsatzTV/client-app/src/plugins/i18n.ts

@ -1,27 +0,0 @@ @@ -1,27 +0,0 @@
import Vue from 'vue';
import VueI18n, { LocaleMessages } from 'vue-i18n';
Vue.use(VueI18n);
function loadLocaleMessages(): LocaleMessages {
const locales = require.context(
'../locales',
true,
/[A-Za-z0-9-_,\s]+\.json$/i
);
const messages: LocaleMessages = {};
locales.keys().forEach((key) => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i);
if (matched && matched.length > 1) {
const locale = matched[1];
messages[locale] = locales(key);
}
});
return messages;
}
export default new VueI18n({
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages()
});

33
ErsatzTV/client-app/src/plugins/vuetify.ts

@ -1,33 +0,0 @@ @@ -1,33 +0,0 @@
import Vue from 'vue';
import Vuetify from 'vuetify';
import colors from 'vuetify/lib/util/colors';
import VueI18n from './i18n';
Vue.use(Vuetify);
export default new Vuetify({
icons: {
iconfont: 'mdi' // default - only for display purposes
},
theme: {
themes: {
dark: {
primary: '#c0c000',
secondary: '#00c0c0',
accent: colors.yellow.accent2,
error: colors.red,
warning: colors.orange,
info: colors.lightBlue,
success: colors.green,
background: '#121212'
}
},
options: {
customProperties: true
},
dark: true
},
lang: {
t: (key, ...params) => VueI18n.t(key, params).toString()
}
});

252
ErsatzTV/client-app/src/router/index.js

@ -1,252 +0,0 @@ @@ -1,252 +0,0 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import HomePage from '../views/HomePage.vue';
import ChannelsPage from '../views/ChannelsPage.vue';
import FFmpegProfilesPage from '../views/FFmpegProfilesPage.vue';
import AddEditFFmpegProfilePage from '../views/AddEditFFmpegProfilePage.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/',
name: 'home.title',
component: HomePage,
meta: {
icon: 'mdi-home',
disabled: false
}
},
{
path: '/channels',
name: 'channels.title',
component: ChannelsPage,
meta: {
icon: 'mdi-broadcast',
disabled: false
}
},
{
path: '/ffmpeg-profiles',
name: 'ffmpeg-profiles.title',
component: FFmpegProfilesPage,
meta: {
icon: 'mdi-video-input-component',
disabled: false
},
showchildren: false
},
{
path: '/watermarks',
name: 'watermarks.title',
meta: {
icon: 'mdi-watermark',
disabled: true
}
},
{
path: '/sources',
name: 'media-sources.title',
meta: {
icon: 'mdi-server-network',
disabled: false
},
showchildren: true,
children: [
{
path: '/sources/local',
name: 'media-sources.local.title',
meta: {
icon: 'mdi-folder',
disabled: true
}
},
{
path: '/sources/emby',
name: 'media-sources.emby.title',
meta: {
icon: 'mdi-emby',
disabled: true
}
},
{
path: '/sources/jellyfin',
name: 'media-sources.jellyfin.title',
meta: {
icon: 'mdi-jellyfish',
disabled: true
}
},
{
path: '/sources/plex',
name: 'media-sources.plex.title',
meta: {
icon: 'mdi-plex',
disabled: true
}
}
]
},
{
path: '/media',
name: 'media.title',
meta: {
icon: 'mdi-cog',
disabled: false
},
showchildren: true,
children: [
{
path: '/media/libraries',
name: 'media.libraries.title',
meta: {
icon: 'mdi-library',
disabled: true
}
},
{
path: '/media/trash',
name: 'media.trash.title',
meta: {
icon: 'mdi-trash-can',
disabled: true
}
},
{
path: '/media/tv-shows',
name: 'media.tv-shows.title',
meta: {
icon: 'mdi-television-classic',
disabled: true
}
},
{
path: '/media/movies',
name: 'media.movies.title',
meta: {
icon: 'mdi-movie',
disabled: true
}
},
{
path: '/media/music-videos',
name: 'media.music-videos.title',
meta: {
icon: 'mdi-music-circle',
disabled: true
}
},
{
path: '/media/other-videos',
name: 'media.other-videos.title',
meta: {
icon: 'mdi-video',
disabled: true
}
},
{
path: '/media/songs',
name: 'media.songs.title',
meta: {
icon: 'mdi-album',
disabled: true
}
}
]
},
{
path: '/lists',
name: 'lists.title',
meta: {
icon: 'mdi-format-list-bulleted',
disabled: false
},
showchildren: true,
children: [
{
path: '/lists/collections',
name: 'lists.collections.title',
meta: {
icon: 'mdi-collage',
disabled: true
}
},
{
path: '/lists/trakt-lists',
name: 'lists.trakt-lists.title',
meta: {
icon: 'mdi-hammer',
disabled: true
}
},
{
path: '/lists/filler-presets',
name: 'lists.filler-presets.title',
meta: {
icon: 'mdi-tune-vertical',
disabled: true
}
}
]
},
{
path: '/schedules',
name: 'schedules.title',
meta: {
icon: 'mdi-calendar',
disabled: true
}
},
{
path: '/playouts',
name: 'playouts.title',
meta: {
icon: 'mdi-clipboard-play-multiple',
disabled: true
}
},
{
path: '/settings',
name: 'settings.title',
meta: {
icon: 'mdi-cog',
disabled: true
}
},
{
path: '/Logs',
name: 'logs.title',
meta: {
icon: 'mdi-card-text',
disabled: true
}
},
//hidden routes - used for non-menu routes
{
path: '/add-ffmpeg-profile',
name: 'add-ffmpeg-profile',
component: AddEditFFmpegProfilePage,
meta: {
disabled: false,
hidden: true
}
},
{
path: '/edit-ffmpeg-profile',
name: 'edit-ffmpeg',
component: AddEditFFmpegProfilePage,
meta: {
disabled: false,
hidden: true
},
props: (route) => ({ query: route.query.id })
}
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
});
export default router;

72
ErsatzTV/client-app/src/services/AbstractApiService.ts

@ -1,72 +0,0 @@ @@ -1,72 +0,0 @@
import type { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
import axios from 'axios';
export function isAxiosError(value: any): value is AxiosError {
return typeof value?.response === 'object';
}
export abstract class AbstractApiService {
protected readonly http: AxiosInstance;
protected constructor(
protected readonly path?: string,
protected readonly baseURL: string = process.env.VUE_APP_ETV_BASE_URL ??
'/'
) {
if (path) {
this.baseURL += path;
}
this.http = axios.create({
baseURL
// ... further stuff, e.g. `withCredentials: true`
});
this.http.defaults.headers.common['Accept'] =
'application/json;charset=UTF-8';
this.http.defaults.headers.common['Content-Type'] =
'application/json;charset=UTF-8';
}
protected createParams(record: Record<string, any>): URLSearchParams {
const params: URLSearchParams = new URLSearchParams();
for (const key in record) {
if (Object.prototype.hasOwnProperty.call(record, key)) {
const value: any = record[key];
if (value !== null && value !== undefined) {
params.append(key, value);
} else {
console.debug(
`Param key '${key}' was null or undefined and will be ignored`
);
}
}
}
return params;
}
protected handleResponse<T>(response: AxiosResponse<T>): T {
return response.data;
}
protected handleError(error: unknown): never {
if (error instanceof Error) {
if (isAxiosError(error)) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
throw error;
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser
console.log(error.request);
throw new Error(error as any);
}
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
throw new Error(error.message);
}
}
throw new Error(error as any);
}
}

17
ErsatzTV/client-app/src/services/ChannelService.ts

@ -1,17 +0,0 @@ @@ -1,17 +0,0 @@
import { AbstractApiService } from './AbstractApiService';
import { Channel } from '@/models/Channel';
class ChannelApiService extends AbstractApiService {
public constructor() {
super();
}
public getAll(): Promise<Channel[]> {
return this.http
.get('/api/channels')
.then(this.handleResponse.bind(this))
.catch(this.handleError.bind(this));
}
}
export const channelApiService: ChannelApiService = new ChannelApiService();

114
ErsatzTV/client-app/src/services/FFmpegProfileService.ts

@ -1,114 +0,0 @@ @@ -1,114 +0,0 @@
import { AbstractApiService } from './AbstractApiService';
import { FFmpegProfile } from '@/models/FFmpegProfile';
import { FFmpegFullProfile } from '../models/FFmpegFullProfile';
class FFmpegProfileApiService extends AbstractApiService {
public constructor() {
super();
}
public getAll(): Promise<FFmpegProfile[]> {
return this.http
.get('/api/ffmpeg/profiles')
.then(this.handleResponse.bind(this))
.catch(this.handleError.bind(this));
}
public getOne(id: string): Promise<FFmpegFullProfile[]> {
return this.http
.get('/api/ffmpeg/profiles/' + id)
.then(this.handleResponse.bind(this))
.catch(this.handleError.bind(this));
}
public newFFmpegProfile(
Name: string,
ThreadCount: number,
HardwareAcceleration: number,
VaapiDriver: number,
VaapiDevice: string,
ResolutionId: number,
VideoFormat: number,
VideoBitrate: number,
VideoBufferSize: number,
AudioFormat: number,
AudioBitrate: number,
AudioBufferSize: number,
NormalizeLoudness: boolean,
AudioChannels: number,
AudioSampleRate: number,
NormalizeFramerate: boolean,
DeinterlaceVideo: boolean
) {
const data = {
Name: Name,
ThreadCount: ThreadCount,
HardwareAcceleration: HardwareAcceleration,
VaapiDriver: VaapiDriver,
VaapiDevice: VaapiDevice,
ResolutionId: ResolutionId,
VideoFormat: VideoFormat,
VideoBitrate: VideoBitrate,
VideoBufferSize: VideoBufferSize,
AudioFormat: AudioFormat,
AudioBitrate: AudioBitrate,
AudioBufferSize: AudioBufferSize,
NormalizeLoudness: NormalizeLoudness,
AudioChannels: AudioChannels,
AudioSampleRate: AudioSampleRate,
NormalizeFramerate: NormalizeFramerate,
DeinterlaceVideo: DeinterlaceVideo
};
this.http.post('/api/ffmpeg/profiles/new', data);
}
public updateFFmpegProfile(
Id: number,
Name: string,
ThreadCount: number,
HardwareAcceleration: number,
VaapiDriver: number,
VaapiDevice: string,
ResolutionId: number,
VideoFormat: number,
VideoBitrate: number,
VideoBufferSize: number,
AudioFormat: number,
AudioBitrate: number,
AudioBufferSize: number,
NormalizeLoudness: boolean,
AudioChannels: number,
AudioSampleRate: number,
NormalizeFramerate: boolean,
DeinterlaceVideo: boolean
) {
const data = {
Id: Id,
Name: Name,
ThreadCount: ThreadCount,
HardwareAcceleration: HardwareAcceleration,
VaapiDriver: VaapiDriver,
VaapiDevice: VaapiDevice,
ResolutionId: ResolutionId,
VideoFormat: VideoFormat,
VideoBitrate: VideoBitrate,
VideoBufferSize: VideoBufferSize,
AudioFormat: AudioFormat,
AudioBitrate: AudioBitrate,
AudioBufferSize: AudioBufferSize,
NormalizeLoudness: NormalizeLoudness,
AudioChannels: AudioChannels,
AudioSampleRate: AudioSampleRate,
NormalizeFramerate: NormalizeFramerate,
DeinterlaceVideo: DeinterlaceVideo
};
this.http.put('/api/ffmpeg/profiles/update', data);
}
public deleteRecord(id: string) {
this.http.delete('/api/ffmpeg/delete/' + id);
}
}
export const ffmpegProfileApiService: FFmpegProfileApiService =
new FFmpegProfileApiService();

16
ErsatzTV/client-app/src/services/VersionService.ts

@ -1,16 +0,0 @@ @@ -1,16 +0,0 @@
import { AbstractApiService } from './AbstractApiService';
class VersionApiService extends AbstractApiService {
public constructor() {
super();
}
public version(): Promise<string> {
return this.http
.get('/api/version')
.then(this.handleResponse.bind(this))
.catch(this.handleError.bind(this));
}
}
export const versionApiService: VersionApiService = new VersionApiService();

11
ErsatzTV/client-app/src/shims-tsx.d.ts vendored

@ -1,11 +0,0 @@ @@ -1,11 +0,0 @@
import Vue, { VNode } from 'vue';
declare global {
namespace JSX {
interface Element extends VNode {}
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}

4
ErsatzTV/client-app/src/shims-vue.d.ts vendored

@ -1,4 +0,0 @@ @@ -1,4 +0,0 @@
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}

46
ErsatzTV/client-app/src/stores/applicationState.js

@ -1,46 +0,0 @@ @@ -1,46 +0,0 @@
import { defineStore } from 'pinia';
import { versionApiService } from '@/services/VersionService';
const originURL = `${window.location.origin}`;
export const applicationState = defineStore('appState', {
state: () => {
return {
miniMenu: false,
currentVersion: 'unknown',
m3uURL: originURL + '/iptv/channels.m3u', // this will need to be fixed for reverse proxies
xmlURL: originURL + '/iptv/xmltv.xml', // this will need to be fixed for reverse proxies
documentationURL: 'https://ersatztv.org/',
githubURL: 'https://github.com/ErsatzTV/ErsatzTV',
discordURL: 'https://discord.gg/hHaJm3yGy6'
};
},
getters: {
isNavigationMini(state) {
return state.miniMenu;
},
currentServerVersion(state) {
return state.currentVersion;
},
navBarURLs(state) {
return {
m3uURL: state.m3uURL,
xmlURL: state.xmlURL,
documentationURL: state.documentationURL,
githubURL: state.githubURL,
discordURL: state.discordURL
};
}
},
actions: {
toggleMiniNavigation() {
this.miniMenu = !this.miniMenu;
},
disableMiniNavigation() {
this.miniMenu = false;
},
async getVersion() {
this.currentVersion = await versionApiService.version();
}
}
});

28
ErsatzTV/client-app/src/stores/languageState.js

@ -1,28 +0,0 @@ @@ -1,28 +0,0 @@
import { defineStore } from 'pinia';
import VueI18n from '../plugins/i18n';
export const languageState = defineStore('languageState', {
state: () => {
return {
language: 'en'
};
},
getters: {
currentLanguageCode: (state) => {
return state.language;
}
},
actions: {
setLanguage(languageCode) {
this.language = languageCode;
VueI18n.locale = languageCode;
}
},
persist: {
afterRestore: (context) => {
if (context.store.language) {
VueI18n.locale = context.store.language;
}
}
}
});

27
ErsatzTV/client-app/src/stores/snackbarState.js

@ -1,27 +0,0 @@ @@ -1,27 +0,0 @@
import { defineStore } from 'pinia';
export const snackbarState = defineStore('snackbarState', {
state: () => {
return {
visible: false,
message: ''
};
},
getters: {
isVisible(state) {
return state.visible;
},
currentMessage(state) {
return state.message;
}
},
actions: {
showSnackbar(message) {
this.message = message;
this.visible = true;
},
closeSnackbar() {
this.visible = false;
}
}
});

909
ErsatzTV/client-app/src/views/AddEditFFmpegProfilePage.vue

@ -1,909 +0,0 @@ @@ -1,909 +0,0 @@
<template>
<div id="AddEditFFmpegProfile">
<h1 id="title" class="mx-4" />
<v-divider color="success" class="ma-2"></v-divider>
<v-app>
<v-form
ref="form"
v-model="isFormValid"
id="ffmpegForm"
lazy-validation
>
<v-container>
<v-row justify="center">
<v-flex shrink class="pb-10">
<v-container style="max-height: 80px">
<v-row justify="center">
<h2 class="mx">
{{ $t('edit-ffmpeg-profile.General') }}
</h2>
</v-row>
</v-container>
<v-container style="max-height: 80px">
<v-text-field
v-model="newProfile.name"
:rules="textRules"
:label="$t('edit-ffmpeg-profile.Name')"
required
></v-text-field>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-text-field
ref="myThreadCount"
v-model="threadCount"
:label="
$t('edit-ffmpeg-profile.thread-count')
"
:rules="validInt"
number
required
></v-text-field>
<v-tooltip v-model="threadCountShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container style="max-height: 40px">
<v-select
v-model="selectedResolution"
@change="preferredResolutionChange"
:items="preferredResolutions"
item-value="id"
item-text="name"
:label="
$t(
'edit-ffmpeg-profile.preferred-resolution'
)
"
required
></v-select>
</v-container>
</v-flex>
<v-divider
style="max-height: 500px"
vertical
color="grey"
></v-divider>
<v-flex shrink class="pb-10">
<v-container style="max-height: 80px">
<v-row justify="center">
<h2 class="mx">
{{ $t('edit-ffmpeg-profile.Video') }}
</h2>
</v-row>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-select
v-model="selectedVideoFormat"
@change="videoFormatChanged"
:items="videoFormats"
item-value="id"
item-text="name"
id="videoFormatSelector"
:label="$t('edit-ffmpeg-profile.format')"
required
></v-select>
<v-tooltip v-model="videoFormatShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-text-field
v-model="newProfile.videoBitRate"
:label="$t('edit-ffmpeg-profile.bitrate')"
:rules="validIntNonZero"
required
></v-text-field>
<v-tooltip v-model="videoBitRateShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-text-field
v-model="newProfile.videoBufferSize"
:label="
$t('edit-ffmpeg-profile.buffer-size')
"
:rules="validIntNonZero"
required
></v-text-field>
<v-tooltip v-model="videoBufferSizeShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-select
v-model="selectedHardwareAcceleration"
@change="hardwareAccelerationChanged"
:items="hardwareAccelerations"
item-value="id"
item-text="name"
:label="
$t(
'edit-ffmpeg-profile.hardware-acceleration'
)
"
required
></v-select>
<v-tooltip
v-model="hardwareAccelerationShow"
top
>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-select
v-model="selectedVaapiDriver"
@change="vaapiDriverChanged"
:items="vaapiDrivers"
:disabled="vaapiDriverDisabled"
item-value="id"
item-text="name"
:label="
$t('edit-ffmpeg-profile.vaapi-driver')
"
required
></v-select>
<v-tooltip v-model="vaapiDriverShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-select
v-model="selectedVaapiDevice"
@change="vaapiDeviceChanged"
:items="vaapiDevices"
:disabled="vaapiDriverDisabled"
:label="
$t('edit-ffmpeg-profile.vaapi-device')
"
required
></v-select>
<v-tooltip v-model="vaapiDeviceShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 50px"
>
<v-checkbox
class="mr-2"
v-model="normalizeFrameRate"
:label="
$t(
'edit-ffmpeg-profile.normalize-framerate'
)
"
color="green lighten-1"
required
></v-checkbox>
<v-tooltip v-model="normalizeFrameRateShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pt-8"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 50px"
>
<v-checkbox
class="mr-2"
v-model="autoDeinterlaceVideo"
:label="
$t(
'edit-ffmpeg-profile.auto-deinterlace-video'
)
"
color="green lighten-1"
required
></v-checkbox>
<v-tooltip
v-model="autoDeinterlaceVideoShow"
top
>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pt-8"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
</v-flex>
<v-divider
style="max-height: 500px"
vertical
color="grey"
></v-divider>
<v-flex shrink class="pb-10">
<v-container style="max-height: 80px">
<v-row justify="center">
<h2 class="mx">
{{ $t('edit-ffmpeg-profile.Audio') }}
</h2>
</v-row>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-select
v-model="selectedAudioFormat"
@change="audioFormatChanged"
:items="audioFormats"
item-value="id"
item-text="name"
:label="$t('edit-ffmpeg-profile.format')"
ref="audioFormat"
required
></v-select>
<v-tooltip v-model="audioFormatShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-text-field
v-model="newProfile.audioBitRate"
:label="$t('edit-ffmpeg-profile.bitrate')"
:rules="validIntNonZero"
required
></v-text-field>
<v-tooltip v-model="audioBitRateShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-text-field
v-model="newProfile.audioBufferSize"
:label="
$t('edit-ffmpeg-profile.buffer-size')
"
:rules="validIntNonZero"
required
></v-text-field>
<v-tooltip v-model="audioBufferSizeShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-text-field
v-model="newProfile.channels"
:label="$t('edit-ffmpeg-profile.channels')"
:rules="validIntNonZero"
required
></v-text-field>
<v-tooltip v-model="audioChannelsShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-text-field
v-model="newProfile.audioSampleRate"
:label="
$t('edit-ffmpeg-profile.sample-rate')
"
:rules="validIntNonZero"
required
></v-text-field>
<v-tooltip v-model="audioSampleRateShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-container
class="d-flex"
style="max-height: 80px"
>
<v-checkbox
v-model="normalizeLoudness"
:label="
$t(
'edit-ffmpeg-profile.normalize-loudness'
)
"
color="green lighten-1"
required
></v-checkbox>
<v-tooltip v-model="normalizeLoudnessShow" top>
<template v-slot:activator="{ on, attrs }">
<v-icon
class="pl-2"
color="grey"
v-ripple="false"
:retain-focus="false"
v-bind="attrs"
v-on="on"
>
mdi-help-circle-outline
</v-icon>
</template>
<span>Recommended Thread Count: 0</span>
</v-tooltip>
</v-container>
<v-spacer style="height: 80px"></v-spacer>
<v-container>
<v-btn
color="green lighten-1"
class="ma-2"
:disabled="!isFormValid"
@click="saveFFmpegProfile()"
>
{{ $t('edit-ffmpeg-profile.save-profile') }}
</v-btn>
<v-btn
color="cancel"
class="ma-2"
@click="cancelAdd()"
>
{{ $t('edit-ffmpeg-profile.cancel') }}
</v-btn>
<v-btn
color="indigo accent-1"
class="ma-2"
@click="cancelAdd()"
>
{{ $t('edit-ffmpeg-profile.help') }}
</v-btn>
</v-container>
</v-flex>
</v-row>
</v-container>
</v-form>
</v-app>
</div>
</template>
<script lang="ts">
import { Vue, Component, Watch } from 'vue-property-decorator';
import { ffmpegProfileApiService } from '@/services/FFmpegProfileService';
@Component
export default class AddEditFFmpegProfile extends Vue {
//@Name({ default: 'AddEditFFmpegProfile' }) AddEditFFmpegProfile!: string;
//@Prop({ default: -1 }) private id!: number;
public newProfile: any = {};
private refForm: any = this.$refs.form;
public isFormValid = false;
public threadCountShow = false;
public videoFormatShow = false;
public videoBitRateShow = false;
public videoBufferSizeShow = false;
public hardwareAccelerationShow = false;
public vaapiDriverShow = false;
public vaapiDeviceShow = false;
public normalizeFrameRateShow = false;
public autoDeinterlaceVideoShow = false;
public audioFormatShow = false;
public audioBitRateShow = false;
public audioBufferSizeShow = false;
public audioChannelsShow = false;
public audioSampleRateShow = false;
public normalizeLoudnessShow = false;
public AddEditFFmpegProfile() {
console.log('test');
}
public audioFormats: [
{ id: number; name: string },
{ id: number; name: string }
] = [
{ id: 1, name: 'aac' },
{ id: 2, name: 'ac3' }
];
private _selectedAudioFormat: number = 2;
public selectedAudioFormat: { id: number; name: string } = {
id: 2,
name: 'ac3'
};
public preferredResolutions: [
{ id: number; name: string },
{ id: number; name: string },
{ id: number; name: string },
{ id: number; name: string }
] = [
{ id: 0, name: '720x480' },
{ id: 1, name: '1280x720' },
{ id: 2, name: '1920x1080' },
{ id: 3, name: '3840x2160' }
];
private _selectedResolution: number = 2;
public selectedResolution: { id: number; name: string } = {
id: 2,
name: '1920x1080'
};
public videoFormats: [
{ id: number; name: string },
{ id: number; name: string },
{ id: number; name: string }
] = [
{ id: 1, name: 'h264' },
{ id: 2, name: 'hevc' },
{ id: 3, name: 'mpeg-2' }
];
private _selectedVideoFormat: number = 1;
public selectedVideoFormat: { id: number; name: string } = {
id: 1,
name: 'h264'
};
public hardwareAccelerations: [
{ id: number; name: string },
{ id: number; name: string },
{ id: number; name: string },
{ id: number; name: string },
{ id: number; name: string }
] = [
{ id: 0, name: 'None' },
{ id: 1, name: 'Qsv' },
{ id: 2, name: 'Nvenc' },
{ id: 3, name: 'Vaapi' },
{ id: 4, name: 'VideoToolbox' }
];
private _selectedHardwareAcceleration: number = 0;
public selectedHardwareAcceleration: { id: number; name: string } = {
id: 0,
name: 'None'
};
public vaapiDrivers: [
{ id: number; name: string },
{ id: number; name: string },
{ id: number; name: string },
{ id: number; name: string },
{ id: number; name: string }
] = [
{ id: 0, name: 'Default' },
{ id: 1, name: 'iHD' },
{ id: 2, name: 'i965' },
{ id: 3, name: 'RadeonSI' },
{ id: 4, name: 'Nouveau' }
];
public vaapiDriverDisabled = true;
private _selectedVaapiDriver: number = 0;
public selectedVaapiDriver: { id: number; name: string } = {
id: 0,
name: 'Default'
};
public selectedVaapiDevice: string = '';
public vaapiDevices: [string, string] = ['', '/dev/dri/renderD128'];
public normalizeFrameRate: boolean = false;
public autoDeinterlaceVideo: boolean = true;
public normalizeLoudness: boolean = true;
saveFFmpegProfile() {
//this means we're adding
if (isNaN(this.id)) {
ffmpegProfileApiService.newFFmpegProfile(
this.newProfile.name,
this.threadCount,
this._selectedHardwareAcceleration,
this._selectedVaapiDriver,
this.selectedVaapiDevice,
this._selectedResolution,
this._selectedVideoFormat,
this.newProfile.videoBitRate,
this.newProfile.videoBufferSize,
this._selectedAudioFormat,
this.newProfile.audioBitRate,
this.newProfile.audioBufferSize,
this.normalizeLoudness,
this.newProfile.channels,
this.newProfile.audioSampleRate,
this.normalizeFrameRate,
this.autoDeinterlaceVideo
);
} else {
//this means we're editing
ffmpegProfileApiService.updateFFmpegProfile(
this.id,
this.newProfile.name,
this.threadCount,
this._selectedHardwareAcceleration,
this._selectedVaapiDriver,
this.selectedVaapiDevice,
this._selectedResolution,
this._selectedVideoFormat,
this.newProfile.videoBitRate,
this.newProfile.videoBufferSize,
this._selectedAudioFormat,
this.newProfile.audioBitRate,
this.newProfile.audioBufferSize,
this.normalizeLoudness,
this.newProfile.channels,
this.newProfile.audioSampleRate,
this.normalizeFrameRate,
this.autoDeinterlaceVideo
);
}
const Toast = this.$swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener('mouseenter', this.$swal.stopTimer);
toast.addEventListener('mouseleave', this.$swal.resumeTimer);
}
});
Toast.fire({
icon: 'success',
title: this.$t('edit-ffmpeg-profile.profile-saved').toString()
});
this.$router.push({
name: 'ffmpeg-profiles.title'
});
}
cancelAdd() {
this.$router.push({
name: 'ffmpeg-profiles.title'
});
}
//~change events~//
public audioFormatChanged(selectObj: number) {
this._selectedAudioFormat = selectObj;
}
public preferredResolutionChange(selectObj: number) {
this._selectedResolution = selectObj + 1;
}
public videoFormatChanged(selectObj: number) {
this._selectedVideoFormat = selectObj;
}
public hardwareAccelerationChanged(selectObj: number) {
this._selectedHardwareAcceleration = selectObj;
this.applyVaapiValidation();
}
public applyVaapiValidation() {
//If they pick VAAPI as the hardware acceleration,
//they can now choose a vaapi driver and device.
//If not, they cannot change the default options.
if (this._selectedHardwareAcceleration == 3) {
this.vaapiDriverDisabled = false;
} else {
this.vaapiDriverDisabled = true;
this._selectedVaapiDriver = 0;
this.selectedVaapiDriver = { id: 0, name: 'Default' };
this.selectedVaapiDevice = '';
}
}
public vaapiDriverChanged(selectObj: number) {
this._selectedVaapiDriver = selectObj;
}
public vaapiDeviceChanged(selectObj: string) {
this.selectedVaapiDevice = selectObj;
}
//~ end change events~//
public threadCount = 0;
get validIntNonZero() {
return [
(v: any) =>
(v && /^[0-9]+$/.test(v)) || this.$t('Must be a valid number.'),
(v: any) => (v && v > 0) || 'Must be greater than 0.'
];
}
get validInt() {
return [
(v: any) =>
(v && /^[0-9]+$/.test(v)) || this.$t('Must be a valid number.')
];
}
get textRules() {
return [(v: any) => (v && v.length > 0) || 'Value must not be empty.'];
}
props!: { id: number };
@Watch('id', { immediate: true }) async onItemChanged() {
console.log('ID', this.id);
this.id = Number(this.$route.query.id) ?? -1;
await this.loadPage();
}
private loaded = false;
private id = -1;
title: string = 'Modify FFmpeg Profile';
async loadPage(): Promise<void> {
if (this.loaded) {
return;
}
var title = document.getElementById('title');
if (title === null || title === undefined) {
//sometimes the element isn't loaded yet, it'll come
//back to this when it's good to go. So skip for now.
return;
}
if (!isNaN(this.id)) {
title.innerHTML = this.$t(
'edit-ffmpeg-profile.edit-profile'
).toString();
var ffmpegFullProfile = await ffmpegProfileApiService.getOne(
this.id.toString()
);
var result = ffmpegFullProfile[0];
if (result !== undefined) {
//We have a profile, let's load it.
this.threadCount = result.threadCount;
this.selectedVaapiDevice = result.vaapiDevice;
this.autoDeinterlaceVideo = result.deinterlaceVideo;
this.normalizeFrameRate = result.normalizeFramerate;
this.normalizeLoudness = result.normalizeLoudness;
this.newProfile = {
name: result.name,
videoBitRate: result.videoBitrate,
videoBufferSize: result.videoBufferSize,
audioBitRate: result.audioBitrate,
audioBufferSize: result.audioBufferSize,
channels: result.audioChannels,
audioSampleRate: result.audioSampleRate
};
this._selectedAudioFormat = result.audioFormat;
this.selectedAudioFormat =
this.audioFormats[result.audioFormat - 1];
this._selectedVideoFormat = result.videoFormat;
this.selectedVideoFormat =
this.videoFormats[result.videoFormat - 1];
this._selectedResolution = result.resolutionId;
this.selectedResolution =
this.preferredResolutions[result.resolutionId - 1];
this._selectedHardwareAcceleration =
result.hardwareAcceleration;
this.selectedHardwareAcceleration =
this.hardwareAccelerations[result.hardwareAcceleration];
this._selectedVaapiDriver = result.vaapiDriver;
this.selectedVaapiDriver =
this.vaapiDrivers[result.vaapiDriver];
this.applyVaapiValidation();
this.loaded = true;
} else {
//an ID was entered (probably in the URL) that doesn't exist. Let's returnt to the profile list.
console.log('No ffmpeg profile found for ID: ' + this.id);
this.$router.push({
name: 'ffmpeg-profiles.title'
});
}
} else {
//new profile!
title.innerHTML = this.$t(
'edit-ffmpeg-profile.add-profile'
).toString();
this._selectedAudioFormat = 2;
this._selectedResolution = 3;
this._selectedVideoFormat = 1;
this._selectedHardwareAcceleration = 0;
this._selectedVaapiDriver = 0;
this.selectedVaapiDevice = '';
this.newProfile = {
name: 'New Profile',
videoBitRate: 2000,
videoBufferSize: 4000,
audioBitRate: 192,
audioBufferSize: 384,
channels: 2,
audioSampleRate: 48
};
this.loaded = true;
}
}
}
</script>

62
ErsatzTV/client-app/src/views/ChannelsPage.vue

@ -1,62 +0,0 @@ @@ -1,62 +0,0 @@
<template>
<div>
<v-data-table
:headers="headers"
:items="channels"
:sort-by="['number']"
class="elevation-1"
>
<template v-slot:[`item.actions`]="{ item }">
<v-btn icon class="mr-2" @click="editRow(item.id)">
<v-icon>mdi-lead-pencil</v-icon>
</v-btn>
<v-btn icon @click.stop="deleteRow(item.id)">
<v-icon>mdi-delete</v-icon></v-btn
>
</template>
</v-data-table>
</div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
import { Channel } from '@/models/Channel';
import { channelApiService } from '@/services/ChannelService';
@Component
export default class Channels extends Vue {
private channels: Channel[] = [];
get headers() {
return [
{ text: this.$t('channels.table.number'), value: 'number' },
{ text: this.$t('channels.table.logo'), value: 'logo' },
{ text: this.$t('channels.table.name'), value: 'name' },
{ text: this.$t('channels.table.language'), value: 'language' },
{ text: this.$t('channels.table.mode'), value: 'streamingMode' },
{
text: this.$t('channels.table.ffmpeg-profile'),
value: 'ffmpegProfile'
},
{ text: 'Actions', value: 'actions', sortable: false }
];
}
deleteRow(item: any) {
let index = this.channels.findIndex((it) => it.id === item.id);
this.channels.splice(index, 1);
}
editRow(item: any) {
let index = this.channels.findIndex((it) => it.id === item.id);
this.channels.splice(index, 1);
}
title: string = 'Channels';
async mounted(): Promise<void> {
this.channels = await channelApiService.getAll();
}
}
</script>

123
ErsatzTV/client-app/src/views/FFmpegProfilesPage.vue

@ -1,123 +0,0 @@ @@ -1,123 +0,0 @@
<template>
<div>
<v-btn color="success" class="ma-4" @click="addRecord()">{{
$t('ffmpeg-profiles.add-profile')
}}</v-btn>
<v-data-table
:headers="headers"
:items="ffmpegProfiles"
:sort-by="['name']"
class="elevation-1"
>
<template v-slot:[`item.actions`]="{ item }">
<v-btn icon class="mr-2" @click="editRow(item.id)">
<v-icon>mdi-lead-pencil</v-icon>
</v-btn>
<v-btn icon @click.stop="deleteRecord(item.id, item.name)">
<v-icon>mdi-delete</v-icon></v-btn
>
</template>
</v-data-table>
</div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
import { FFmpegProfile } from '@/models/FFmpegProfile';
import { ffmpegProfileApiService } from '@/services/FFmpegProfileService';
@Component
export default class FFmpegProfiles extends Vue {
public ffmpegProfiles: FFmpegProfile[] = [];
get headers() {
return [
{ text: this.$t('ffmpeg-profiles.table.name'), value: 'name' },
{
text: this.$t('ffmpeg-profiles.table.resolution'),
value: 'resolution'
},
{ text: this.$t('ffmpeg-profiles.table.video'), value: 'video' },
{ text: this.$t('ffmpeg-profiles.table.audio'), value: 'audio' },
{
text: this.$t('ffmpeg-profiles.actions'),
value: 'actions',
sortable: false
}
];
}
addRecord() {
this.$router.push({
name: 'add-ffmpeg-profile'
});
}
deleteRecord(record: any, recordName: any) {
this.$swal
.fire({
title: this.$t(
'ffmpeg-profiles.delete-dialog-title'
).toString(),
// text: this.$t(
// 'Delete "' + recordName + '" FFmpeg Profile?'
// ).toString(),
text: this.$t('ffmpeg-profiles.delete-dialog-text', {
profileName: '"' + recordName + '"'
}).toString(),
icon: 'warning',
//iconColor: '#4CAF50',
showCancelButton: true,
cancelButtonText: this.$t('ffmpeg-profiles.no').toString(),
confirmButtonText: this.$t('ffmpeg-profiles.yes').toString()
})
.then((result) => {
if (result.isConfirmed) {
let index = this.ffmpegProfiles.findIndex(
(it) => it.id === record
);
this.ffmpegProfiles.splice(index, 1);
ffmpegProfileApiService.deleteRecord(String(record));
const Toast = this.$swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener(
'mouseenter',
this.$swal.stopTimer
);
toast.addEventListener(
'mouseleave',
this.$swal.resumeTimer
);
}
});
Toast.fire({
icon: 'success',
title: this.$t(
'ffmpeg-profiles.profile-deleted'
).toString()
});
}
});
}
editRow(id: any) {
this.$router.push({
name: 'edit-ffmpeg',
query: { id: id }
});
}
title: string = 'FFMpeg Profiles';
async mounted(): Promise<void> {
this.ffmpegProfiles = await ffmpegProfileApiService.getAll();
}
}
</script>

9
ErsatzTV/client-app/src/views/HomePage.vue

@ -1,9 +0,0 @@ @@ -1,9 +0,0 @@
<template>
<div>
<h1>{{ $t(this.$route.name) }}</h1>
</div>
</template>
<script>
export default {};
</script>

44
ErsatzTV/client-app/tsconfig.json

@ -1,44 +0,0 @@ @@ -1,44 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"allowJs": true,
"experimentalDecorators": true,
"jsx": "preserve",
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env",
"vuetify",
"vue-sweetalert2"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

21
ErsatzTV/client-app/vue.config.js

@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
const { defineConfig } = require('@vue/cli-service');
module.exports = defineConfig({
transpileDependencies: ['vuetify'],
runtimeCompiler: true,
pwa: {
name: 'ErsatzTV'
},
publicPath: '/v2/',
outputDir: '../wwwroot/v2',
filenameHashing: false,
pluginOptions: {
i18n: {
locale: 'en',
fallbackLocale: 'en',
localeDir: 'locales',
enableInSFC: true
}
}
});

6
docker/Dockerfile

@ -1,10 +1,6 @@ @@ -1,10 +1,6 @@
# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
RUN apt-get update && apt-get install -y ca-certificates gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs npm
WORKDIR /source
# copy csproj and restore as distinct layers
@ -13,7 +9,6 @@ COPY nuget.config . @@ -13,7 +9,6 @@ COPY nuget.config .
COPY lib/nuget/* ./lib/nuget/
COPY artwork/* ./artwork/
COPY ErsatzTV/*.csproj ./ErsatzTV/
COPY ErsatzTV/client-app/package*.json ./ErsatzTV/client-app/
COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/
COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/
COPY ErsatzTV.FFmpeg/*.csproj ./ErsatzTV.FFmpeg/
@ -37,7 +32,6 @@ WORKDIR /source/ErsatzTV.Scanner @@ -37,7 +32,6 @@ WORKDIR /source/ErsatzTV.Scanner
RUN dotnet publish ErsatzTV.Scanner.csproj -c release -o /app -r linux-x64 --self-contained false --no-restore /p:DebugType=Embedded /p:InformationalVersion=${INFO_VERSION}
WORKDIR /source/ErsatzTV
RUN sed -i '/Scanner/d' ErsatzTV.csproj
RUN node --version
RUN dotnet publish ErsatzTV.csproj -c release -o /app -r linux-x64 --self-contained false --no-restore /p:DebugType=Embedded /p:InformationalVersion=${INFO_VERSION}
# final stage/image

5
docker/arm32v7/Dockerfile

@ -6,10 +6,6 @@ COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet @@ -6,10 +6,6 @@ COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet
# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy-amd64 AS build
RUN apt-get update && apt-get install -y ca-certificates gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs
WORKDIR /source
# copy csproj and restore as distinct layers
@ -18,7 +14,6 @@ COPY nuget.config . @@ -18,7 +14,6 @@ COPY nuget.config .
COPY lib/nuget/* ./lib/nuget/
COPY artwork/* ./artwork/
COPY ErsatzTV/*.csproj ./ErsatzTV/
COPY ErsatzTV/client-app/package*.json ./ErsatzTV/client-app/
COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/
COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/
COPY ErsatzTV.FFmpeg/*.csproj ./ErsatzTV.FFmpeg/

5
docker/arm64/Dockerfile

@ -6,10 +6,6 @@ COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet @@ -6,10 +6,6 @@ COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet
# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy-amd64 AS build
RUN apt-get update && apt-get install -y ca-certificates gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs
WORKDIR /source
# copy csproj and restore as distinct layers
@ -18,7 +14,6 @@ COPY nuget.config . @@ -18,7 +14,6 @@ COPY nuget.config .
COPY lib/nuget/* ./lib/nuget/
COPY artwork/* ./artwork/
COPY ErsatzTV/*.csproj ./ErsatzTV/
COPY ErsatzTV/client-app/package*.json ./ErsatzTV/client-app/
COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/
COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/
COPY ErsatzTV.FFmpeg/*.csproj ./ErsatzTV.FFmpeg/

5
docker/nvidia/Dockerfile

@ -1,10 +1,6 @@ @@ -1,10 +1,6 @@
# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
RUN apt-get update && apt-get install -y ca-certificates gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs npm
WORKDIR /source
# copy csproj and restore as distinct layers
@ -13,7 +9,6 @@ COPY nuget.config . @@ -13,7 +9,6 @@ COPY nuget.config .
COPY lib/nuget/* ./lib/nuget/
COPY artwork/* ./artwork/
COPY ErsatzTV/*.csproj ./ErsatzTV/
COPY ErsatzTV/client-app/package*.json ./ErsatzTV/client-app/
COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/
COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/
COPY ErsatzTV.FFmpeg/*.csproj ./ErsatzTV.FFmpeg/

5
docker/vaapi/Dockerfile

@ -1,10 +1,6 @@ @@ -1,10 +1,6 @@
# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
RUN apt-get update && apt-get install -y ca-certificates gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs npm
WORKDIR /source
# copy csproj and restore as distinct layers
@ -13,7 +9,6 @@ COPY nuget.config . @@ -13,7 +9,6 @@ COPY nuget.config .
COPY lib/nuget/* ./lib/nuget/
COPY artwork/* ./artwork/
COPY ErsatzTV/*.csproj ./ErsatzTV/
COPY ErsatzTV/client-app/package*.json ./ErsatzTV/client-app/
COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/
COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/
COPY ErsatzTV.FFmpeg/*.csproj ./ErsatzTV.FFmpeg/

2
scripts/cleanup-all-code.sh

@ -3,4 +3,4 @@ @@ -3,4 +3,4 @@
cd "$(git rev-parse --show-cdup)" || exit
dotnet tool restore
dotnet jb cleanupcode ErsatzTV.sln --exclude='CHANGELOG.md;scripts/**;generated/**;ErsatzTV/client-app/**'
dotnet jb cleanupcode ErsatzTV.sln --exclude='CHANGELOG.md;scripts/**;generated/**'

2
scripts/cleanup-code.sh

@ -4,4 +4,4 @@ cd "$(git rev-parse --show-cdup)" || exit @@ -4,4 +4,4 @@ cd "$(git rev-parse --show-cdup)" || exit
dotnet tool restore
CHANGED=$(git status --porcelain | sed 's/^...//' | paste -sd ";" -)
dotnet jb cleanupcode ErsatzTV.sln --exclude='CHANGELOG.md;scripts/**;generated/**;ErsatzTV/client-app/**' --include="$CHANGED"
dotnet jb cleanupcode ErsatzTV.sln --exclude='CHANGELOG.md;scripts/**;generated/**' --include="$CHANGED"

Loading…
Cancel
Save