Browse Source

add v2 ffmpeg profile page (#768)

* wip

* remove transcode property; use i18n

* add api

* use computed table headers for i18n
pull/769/head
Jason Dove 3 years ago committed by GitHub
parent
commit
780ebc01ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      ErsatzTV.Application/FFmpegProfiles/Mapper.cs
  2. 5
      ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApi.cs
  3. 28
      ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApiHandler.cs
  4. 6
      ErsatzTV.Core/Api/Channels/ChannelResponseModel.cs
  5. 8
      ErsatzTV.Core/Api/FFmpegProfiles/FFmpegProfileResponseModel.cs
  6. 18
      ErsatzTV/Controllers/Api/FFmpegProfileController.cs
  7. 10
      ErsatzTV/client-app/src/locales/en.json
  8. 5
      ErsatzTV/client-app/src/locales/pt-br.json
  9. 7
      ErsatzTV/client-app/src/models/FFmpegProfile.ts
  10. 4
      ErsatzTV/client-app/src/router/index.js
  11. 18
      ErsatzTV/client-app/src/services/FFmpegProfileService.ts
  12. 24
      ErsatzTV/client-app/src/views/ChannelsPage.vue
  13. 40
      ErsatzTV/client-app/src/views/FFmpegProfilesPage.vue
  14. 2
      api/ersatztv-mock-api.json

9
ErsatzTV.Application/FFmpegProfiles/Mapper.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.Application.Resolutions;
using ErsatzTV.Core.Api.FFmpegProfiles;
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Application.FFmpegProfiles;
@ -26,6 +27,14 @@ internal static class Mapper @@ -26,6 +27,14 @@ internal static class Mapper
profile.NormalizeFramerate,
profile.DeinterlaceVideo == true);
internal static FFmpegProfileResponseModel ProjectToResponseModel(FFmpegProfile ffmpegProfile) =>
new(
ffmpegProfile.Id,
ffmpegProfile.Name,
$"{ffmpegProfile.Resolution.Width}x{ffmpegProfile.Resolution.Height}",
ffmpegProfile.VideoFormat.ToString().ToLowerInvariant(),
ffmpegProfile.AudioFormat.ToString().ToLowerInvariant());
private static ResolutionViewModel Project(Resolution resolution) =>
new(resolution.Id, resolution.Name, resolution.Width, resolution.Height);
}

5
ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApi.cs

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
using ErsatzTV.Core.Api.FFmpegProfiles;
namespace ErsatzTV.Application.FFmpegProfiles;
public record GetAllFFmpegProfilesForApi : IRequest<List<FFmpegProfileResponseModel>>;

28
ErsatzTV.Application/FFmpegProfiles/Queries/GetAllFFmpegProfilesForApiHandler.cs

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
using ErsatzTV.Core.Api.FFmpegProfiles;
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.FFmpegProfiles.Mapper;
namespace ErsatzTV.Application.FFmpegProfiles;
public class
GetAllFFmpegProfilesForApiHandler : IRequestHandler<GetAllFFmpegProfilesForApi, List<FFmpegProfileResponseModel>>
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public GetAllFFmpegProfilesForApiHandler(IDbContextFactory<TvContext> dbContextFactory) =>
_dbContextFactory = dbContextFactory;
public async Task<List<FFmpegProfileResponseModel>> Handle(
GetAllFFmpegProfilesForApi request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
List<FFmpegProfile> ffmpegProfiles = await dbContext.FFmpegProfiles
.AsNoTracking()
.Include(p => p.Resolution)
.ToListAsync(cancellationToken);
return ffmpegProfiles.Map(ProjectToResponseModel).ToList();
}
}

6
ErsatzTV.Core/Api/Channels/ChannelResponseModel.cs

@ -1,9 +1,11 @@ @@ -1,9 +1,11 @@
namespace ErsatzTV.Core.Api.Channels;
using Newtonsoft.Json;
namespace ErsatzTV.Core.Api.Channels;
public record ChannelResponseModel(
int Id,
string Number,
string Name,
string FFmpegProfile,
[property: JsonProperty("ffmpegProfile")] string FFmpegProfile,
string Language,
string StreamingMode);

8
ErsatzTV.Core/Api/FFmpegProfiles/FFmpegProfileResponseModel.cs

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
namespace ErsatzTV.Core.Api.FFmpegProfiles;
public record FFmpegProfileResponseModel(
int Id,
string Name,
string Resolution,
string Video,
string Audio);

18
ErsatzTV/Controllers/Api/FFmpegProfileController.cs

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
using ErsatzTV.Application.FFmpegProfiles;
using ErsatzTV.Core.Api.FFmpegProfiles;
using MediatR;
using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
public class FFmpegProfileController
{
private readonly IMediator _mediator;
public FFmpegProfileController(IMediator mediator) => _mediator = mediator;
[HttpGet("/api/ffmpeg/profiles")]
public async Task<List<FFmpegProfileResponseModel>> GetAll() =>
await _mediator.Send(new GetAllFFmpegProfilesForApi());
}

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

@ -86,7 +86,13 @@ @@ -86,7 +86,13 @@
"title": "Home"
},
"ffmpeg-profiles": {
"title": "FFmpeg Profiles"
"title": "FFmpeg Profiles",
"table": {
"name": "Name",
"resolution": "Resolution",
"video": "Video",
"audio": "Audio"
}
},
"watermarks": {
"title": "Watermarks"
@ -165,4 +171,4 @@ @@ -165,4 +171,4 @@
"ffmpeg-profile": "FFmpeg Profile"
}
}
}
}

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

@ -86,7 +86,10 @@ @@ -86,7 +86,10 @@
"title": "Início"
},
"ffmpeg-profiles": {
"title": "Perfis FFmpeg"
"title": "Perfis FFmpeg",
"table": {
"name": "Nome"
}
},
"watermarks": {
"title": "Marcas d'água"

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

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

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

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
import VueRouter from 'vue-router';
import HomePage from '../views/HomePage.vue';
import ChannelsPage from '../views/ChannelsPage.vue';
import FFmpegProfilesPage from '../views/FFmpegProfilesPage.vue';
Vue.use(VueRouter);
@ -27,9 +28,10 @@ const routes = [ @@ -27,9 +28,10 @@ const routes = [
{
path: '/ffmpeg-profiles',
name: 'ffmpeg-profiles.title',
component: FFmpegProfilesPage,
meta: {
icon: 'mdi-video-input-component',
disabled: true
disabled: false
}
},
{

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

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
import { AbstractApiService } from './AbstractApiService';
import { FFmpegProfile } from '@/models/FFmpegProfile';
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));
}
}
export const ffmpegProfileApiService: FFmpegProfileApiService =
new FFmpegProfileApiService();

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

@ -18,17 +18,19 @@ import { channelApiService } from '@/services/ChannelService'; @@ -18,17 +18,19 @@ import { channelApiService } from '@/services/ChannelService';
export default class Channels extends Vue {
private channels: Channel[] = [];
private headers = [
{ 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'
}
];
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'
}
];
}
title: string = 'Channels';

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

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
<template>
<div>
<v-data-table
:headers="headers"
:items="ffmpegProfiles"
:sort-by="['name']"
class="elevation-1"
>
</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 {
private 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' }
];
}
title: string = 'FFMpeg Profiles';
async mounted(): Promise<void> {
this.ffmpegProfiles = await ffmpegProfileApiService.getAll();
}
}
</script>

2
api/ersatztv-mock-api.json

@ -40,7 +40,7 @@ @@ -40,7 +40,7 @@
"responses": [
{
"uuid": "2f42cd38-2591-475f-a4bf-e5fb3455a8b3",
"body": "[\n {{#repeat (faker 'datatype.number' min=2 max=3)}}\n { \n \"id\": {{@index}},\n \"name\": \"{{faker 'hacker.adjective'}} {{faker 'hacker.noun'}}\",\n \"transcode\": {{faker 'datatype.boolean'}},\n \"resolution\": \"{{oneOf (array '1920x1080' '1280x720' '720x480')}}\",\n \"videoCodec\": \"{{oneOf (array 'hevc_nvenc' 'h264_nvenc')}}\",\n \"audioCodec\": \"{{oneOf (array 'aac' 'ac3')}}\"\n }\n {{/repeat}}\n]",
"body": "[\n {{#repeat (faker 'datatype.number' min=2 max=3)}}\n { \n \"id\": {{@index}},\n \"name\": \"{{faker 'hacker.adjective'}} {{faker 'hacker.noun'}}\",\n \"resolution\": \"{{oneOf (array '1920x1080' '1280x720' '720x480')}}\",\n \"video\": \"{{oneOf (array 'hevc' 'h264')}}{{oneOf (array ' / nvenc' ' / qsv' ' / vaapi' '')}}\",\n \"audio\": \"{{oneOf (array 'aac' 'ac3')}}\"\n }\n {{/repeat}}\n]",
"latency": 0,
"statusCode": 200,
"label": "",

Loading…
Cancel
Save