UI rewrite - ffmpeg profiles (#823)
* ffmpeg profile functionality, sweetalert2 * add new files * cleanup controller; remove unused classes * apply formatting * cleanup core project * don't use bom * whitespace * remove generated css * remove generated js/map * Remove attempted linter fix, channels button, watermarks page. Fixed handlerror. * Changed deleted confirmation to toast. * Localized strings for language change. Modified Action icons to buttons and left default sizes. Changed Cancel to No where Yes is an option * lint Co-authored-by: Jason Dove <jason@jasondove.me>
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
|
||||
[*]
|
||||
charset=utf-8-bom
|
||||
charset=utf-8
|
||||
end_of_line=lf
|
||||
trim_trailing_whitespace=false
|
||||
insert_final_newline=false
|
||||
|
||||
@@ -20,7 +20,7 @@ public class CreateFFmpegProfileHandler :
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
Validation<BaseError, FFmpegProfile> validation = await Validate(dbContext, request);
|
||||
return await LanguageExtensions.Apply(validation, profile => PersistFFmpegProfile(dbContext, profile));
|
||||
return await validation.Apply(profile => PersistFFmpegProfile(dbContext, profile));
|
||||
}
|
||||
|
||||
private static async Task<CreateFFmpegProfileResult> PersistFFmpegProfile(
|
||||
|
||||
@@ -35,6 +35,27 @@ internal static class Mapper
|
||||
ffmpegProfile.VideoFormat.ToString().ToLowerInvariant(),
|
||||
ffmpegProfile.AudioFormat.ToString().ToLowerInvariant());
|
||||
|
||||
internal static FFmpegFullProfileResponseModel ProjectToFullResponseModel(FFmpegProfile ffmpegProfile) =>
|
||||
new(
|
||||
ffmpegProfile.Id,
|
||||
ffmpegProfile.Name,
|
||||
ffmpegProfile.ThreadCount,
|
||||
(int)ffmpegProfile.HardwareAcceleration,
|
||||
(int)ffmpegProfile.VaapiDriver,
|
||||
ffmpegProfile.VaapiDevice,
|
||||
ffmpegProfile.ResolutionId,
|
||||
(int)ffmpegProfile.VideoFormat,
|
||||
ffmpegProfile.VideoBitrate,
|
||||
ffmpegProfile.VideoBufferSize,
|
||||
(int)ffmpegProfile.AudioFormat,
|
||||
ffmpegProfile.AudioBitrate,
|
||||
ffmpegProfile.AudioBufferSize,
|
||||
ffmpegProfile.NormalizeLoudness,
|
||||
ffmpegProfile.AudioChannels,
|
||||
ffmpegProfile.AudioSampleRate,
|
||||
ffmpegProfile.NormalizeFramerate,
|
||||
ffmpegProfile.DeinterlaceVideo);
|
||||
|
||||
private static ResolutionViewModel Project(Resolution resolution) =>
|
||||
new(resolution.Id, resolution.Name, resolution.Width, resolution.Height);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
using ErsatzTV.Core.Api.FFmpegProfiles;
|
||||
|
||||
namespace ErsatzTV.Application.FFmpegProfiles;
|
||||
|
||||
public record GetFFmpegFullProfileByIdForApi(int Id) : IRequest<Option<FFmpegFullProfileResponseModel>>;
|
||||
@@ -0,0 +1,28 @@
|
||||
using ErsatzTV.Core.Api.FFmpegProfiles;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using static ErsatzTV.Application.FFmpegProfiles.Mapper;
|
||||
|
||||
namespace ErsatzTV.Application.FFmpegProfiles;
|
||||
|
||||
public class
|
||||
GetFFmpegProfileByIdForApiHandler : IRequestHandler<GetFFmpegFullProfileByIdForApi,
|
||||
Option<FFmpegFullProfileResponseModel>>
|
||||
{
|
||||
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||
|
||||
public GetFFmpegProfileByIdForApiHandler(IDbContextFactory<TvContext> dbContextFactory) =>
|
||||
_dbContextFactory = dbContextFactory;
|
||||
|
||||
public async Task<Option<FFmpegFullProfileResponseModel>> Handle(
|
||||
GetFFmpegFullProfileByIdForApi request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
return await dbContext.FFmpegProfiles
|
||||
.Include(p => p.Resolution)
|
||||
.SelectOneAsync(p => p.Id, p => p.Id == request.Id)
|
||||
.MapT(ProjectToFullResponseModel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace ErsatzTV.Core.Api.FFmpegProfiles;
|
||||
|
||||
public record FFmpegFullProfileResponseModel(
|
||||
int Id,
|
||||
string Name,
|
||||
int ThreadCount,
|
||||
int HardwareAcceleration,
|
||||
int VaapiDriver,
|
||||
string VaapiDevice,
|
||||
int ResolutionId,
|
||||
int VideoFormat,
|
||||
int VideoBitrate,
|
||||
int VideoBufferSize,
|
||||
int AudioFormat,
|
||||
int AudioBitrate,
|
||||
int AudioBufferSize,
|
||||
bool NormalizeLoudness,
|
||||
int AudioChannels,
|
||||
int AudioSampleRate,
|
||||
bool NormalizeFramerate,
|
||||
bool? DeinterlaceVideo);
|
||||
@@ -1,4 +1,6 @@
|
||||
using ErsatzTV.Application.FFmpegProfiles;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using ErsatzTV.Application.FFmpegProfiles;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Core.Api.FFmpegProfiles;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -15,4 +17,21 @@ public class FFmpegProfileController
|
||||
[HttpGet("/api/ffmpeg/profiles")]
|
||||
public async Task<List<FFmpegProfileResponseModel>> GetAll() =>
|
||||
await _mediator.Send(new GetAllFFmpegProfilesForApi());
|
||||
|
||||
[HttpPost("/api/ffmpeg/profiles/new")]
|
||||
public async Task<Either<BaseError, CreateFFmpegProfileResult>> AddOne(
|
||||
[Required] [FromBody]
|
||||
CreateFFmpegProfile request) => await _mediator.Send(request);
|
||||
|
||||
[HttpPut("/api/ffmpeg/profiles/update")]
|
||||
public async Task<Either<BaseError, UpdateFFmpegProfileResult>> UpdateOne(
|
||||
[Required] [FromBody]
|
||||
UpdateFFmpegProfile request) => await _mediator.Send(request);
|
||||
|
||||
[HttpGet("/api/ffmpeg/profiles/{id}")]
|
||||
public async Task<Option<FFmpegFullProfileResponseModel>> GetOne(int id) =>
|
||||
await _mediator.Send(new GetFFmpegFullProfileByIdForApi(id));
|
||||
|
||||
[HttpDelete("/api/ffmpeg/delete/{id}")]
|
||||
public async Task DeleteProfileAsync(int id) => await _mediator.Send(new DeleteFFmpegProfile(id));
|
||||
}
|
||||
|
||||
@@ -116,4 +116,6 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ProjectExtensions><VisualStudio><UserProperties client-app_4package_1json__JsonSchema="https://json.schemastore.org/phraseapp.json" /></VisualStudio></ProjectExtensions>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -21,3 +21,8 @@ pnpm-debug.log*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# generated files
|
||||
src/assets/css/*.css
|
||||
src/models/*.js
|
||||
src/models/*.js.map
|
||||
|
||||
Generated
+46
@@ -9,16 +9,19 @@
|
||||
"version": "0.1.0",
|
||||
"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": {
|
||||
@@ -1965,6 +1968,11 @@
|
||||
"integrity": "sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sweetalert2/theme-dark": {
|
||||
"version": "5.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@sweetalert2/theme-dark/-/theme-dark-5.0.11.tgz",
|
||||
"integrity": "sha512-yw42SqvvFc1ObRVLNLSo5HpJo3vDTYlpS2ba+JkWHRcVIEXDnEOaDfAVRtDPSK75GJkvdz9IdfUrc18zdmiE2w=="
|
||||
},
|
||||
"node_modules/@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
@@ -10843,6 +10851,15 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/sweetalert2": {
|
||||
"version": "11.4.17",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.4.17.tgz",
|
||||
"integrity": "sha512-z0iQW5fDdvtoDNle3iHTKunsuNWgPaMifVu0GPtdguR0uVPrvg9gg9bnSQeJ4tx3C7FwJOEnRshxrs6fFItRvw==",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://sweetalert2.github.io/#donations"
|
||||
}
|
||||
},
|
||||
"node_modules/table": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz",
|
||||
@@ -11936,6 +11953,17 @@
|
||||
"integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vue-sweetalert2": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/vue-sweetalert2/-/vue-sweetalert2-5.0.5.tgz",
|
||||
"integrity": "sha512-Q7+TjQxNwGruT2jfPaSKMz18HHWs81r4umEwlrrtOf/QL5Iwl1T+tUaNkcvKB7Rf9rYftubt7NnOMjfDMnDH3A==",
|
||||
"dependencies": {
|
||||
"sweetalert2": "11.x"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-template-compiler": {
|
||||
"version": "2.6.14",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz",
|
||||
@@ -14142,6 +14170,11 @@
|
||||
"integrity": "sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==",
|
||||
"dev": true
|
||||
},
|
||||
"@sweetalert2/theme-dark": {
|
||||
"version": "5.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@sweetalert2/theme-dark/-/theme-dark-5.0.11.tgz",
|
||||
"integrity": "sha512-yw42SqvvFc1ObRVLNLSo5HpJo3vDTYlpS2ba+JkWHRcVIEXDnEOaDfAVRtDPSK75GJkvdz9IdfUrc18zdmiE2w=="
|
||||
},
|
||||
"@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
@@ -20737,6 +20770,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sweetalert2": {
|
||||
"version": "11.4.17",
|
||||
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.4.17.tgz",
|
||||
"integrity": "sha512-z0iQW5fDdvtoDNle3iHTKunsuNWgPaMifVu0GPtdguR0uVPrvg9gg9bnSQeJ4tx3C7FwJOEnRshxrs6fFItRvw=="
|
||||
},
|
||||
"table": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz",
|
||||
@@ -21552,6 +21590,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-sweetalert2": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/vue-sweetalert2/-/vue-sweetalert2-5.0.5.tgz",
|
||||
"integrity": "sha512-Q7+TjQxNwGruT2jfPaSKMz18HHWs81r4umEwlrrtOf/QL5Iwl1T+tUaNkcvKB7Rf9rYftubt7NnOMjfDMnDH3A==",
|
||||
"requires": {
|
||||
"sweetalert2": "11.x"
|
||||
}
|
||||
},
|
||||
"vue-template-compiler": {
|
||||
"version": "2.6.14",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz",
|
||||
|
||||
@@ -10,16 +10,19 @@
|
||||
},
|
||||
"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": {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
.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;
|
||||
}
|
||||
@@ -2,14 +2,14 @@
|
||||
<v-list nav dense>
|
||||
<span v-for="(nav, i) in navigation" :key="i">
|
||||
<SideBarMenuItem
|
||||
v-if="!nav.children"
|
||||
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
|
||||
v-else-if="nav.children"
|
||||
@click.native="disableMiniNavigation()"
|
||||
:name="nav.name"
|
||||
:icon="nav.meta.icon"
|
||||
|
||||
@@ -50,6 +50,10 @@ export default {
|
||||
children: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
nonmenuchildren: {
|
||||
type: Array,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
|
||||
@@ -87,6 +87,13 @@
|
||||
},
|
||||
"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",
|
||||
@@ -94,6 +101,31 @@
|
||||
"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"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"$vuetify": {
|
||||
"badge": "Distintivo",
|
||||
"close": "Fechar",
|
||||
"Actions": "Ações",
|
||||
"dataIterator": {
|
||||
"noResultsText": "Nenhum dado encontrado",
|
||||
"loadingText": "Carregando itens..."
|
||||
@@ -87,10 +88,45 @@
|
||||
},
|
||||
"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"
|
||||
"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"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Vue from 'vue';
|
||||
import Vue from 'vue';
|
||||
import App from './App.vue';
|
||||
import vuetify from './plugins/vuetify';
|
||||
import router from './router';
|
||||
@@ -8,16 +8,26 @@ 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,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
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;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ 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);
|
||||
|
||||
@@ -32,7 +33,8 @@ const routes = [
|
||||
meta: {
|
||||
icon: 'mdi-video-input-component',
|
||||
disabled: false
|
||||
}
|
||||
},
|
||||
showchildren: false
|
||||
},
|
||||
{
|
||||
path: '/watermarks',
|
||||
@@ -49,6 +51,7 @@ const routes = [
|
||||
icon: 'mdi-server-network',
|
||||
disabled: false
|
||||
},
|
||||
showchildren: true,
|
||||
children: [
|
||||
{
|
||||
path: '/sources/local',
|
||||
@@ -91,6 +94,7 @@ const routes = [
|
||||
icon: 'mdi-cog',
|
||||
disabled: false
|
||||
},
|
||||
showchildren: true,
|
||||
children: [
|
||||
{
|
||||
path: '/media/libraries',
|
||||
@@ -157,6 +161,7 @@ const routes = [
|
||||
icon: 'mdi-format-list-bulleted',
|
||||
disabled: false
|
||||
},
|
||||
showchildren: true,
|
||||
children: [
|
||||
{
|
||||
path: '/lists/collections',
|
||||
@@ -215,6 +220,26 @@ const routes = [
|
||||
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 })
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AbstractApiService } from './AbstractApiService';
|
||||
import { FFmpegProfile } from '@/models/FFmpegProfile';
|
||||
import { FFmpegFullProfile } from '../models/FFmpegFullProfile';
|
||||
|
||||
class FFmpegProfileApiService extends AbstractApiService {
|
||||
public constructor() {
|
||||
@@ -12,6 +13,101 @@ class FFmpegProfileApiService extends AbstractApiService {
|
||||
.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 =
|
||||
|
||||
@@ -0,0 +1,909 @@
|
||||
<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>
|
||||
@@ -5,7 +5,14 @@
|
||||
:items="channels"
|
||||
:sort-by="['number']"
|
||||
class="elevation-1"
|
||||
></v-data-table>
|
||||
>
|
||||
<template v-slot:[`item.actions`]="{ item }">
|
||||
<v-icon small class="mr-2" @click="editRow(item.id)">
|
||||
mdi-lead-pencil
|
||||
</v-icon>
|
||||
<v-icon small @click="deleteRow(item.id)">mdi-delete</v-icon>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -28,10 +35,21 @@ export default class Channels extends Vue {
|
||||
{
|
||||
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> {
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
<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>
|
||||
@@ -17,7 +29,7 @@ import { ffmpegProfileApiService } from '@/services/FFmpegProfileService';
|
||||
|
||||
@Component
|
||||
export default class FFmpegProfiles extends Vue {
|
||||
private ffmpegProfiles: FFmpegProfile[] = [];
|
||||
public ffmpegProfiles: FFmpegProfile[] = [];
|
||||
|
||||
get headers() {
|
||||
return [
|
||||
@@ -27,10 +39,81 @@ export default class FFmpegProfiles extends Vue {
|
||||
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.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> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
@@ -16,7 +16,8 @@
|
||||
"baseUrl": ".",
|
||||
"types": [
|
||||
"webpack-env",
|
||||
"vuetify"
|
||||
"vuetify",
|
||||
"vue-sweetalert2"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
|
||||
Reference in New Issue
Block a user