2 changes: 2 additions & 0 deletions mythtv/html/assets/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"delete_details": "Это удалит правило записи.",
"delete_rule": "Удалить правило записи",
"description": "Описание",
"download": "Скачать запись",
"duration": "Продолжительность",
"edit_metadata": "Изменить метаданные",
"edit_rule": "Изменить правило записи",
Expand Down Expand Up @@ -216,6 +217,7 @@
"time": "Время"
},
"videos": {
"download": "Скачать видео",
"heading": "Видео",
"releasedate": "Дата выпуска"
}
Expand Down
2 changes: 2 additions & 0 deletions mythtv/html/assets/i18n/sl.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"delete_details": "S tem boste izbrisali pravilo snemanja",
"delete_rule": "Izbriši pravilo snemanja",
"description": "Opis",
"download": "Prenos posnetka",
"duration": "Trajanje",
"edit_metadata": "Uredi metapodatke",
"edit_rule": "Uredi pravilo snemanja",
Expand Down Expand Up @@ -216,6 +217,7 @@
"time": "Čas"
},
"videos": {
"download": "Prenesi video",
"heading": "Videoposnetki",
"releasedate": "Datum izdaje"
}
Expand Down
2 changes: 2 additions & 0 deletions mythtv/html/assets/i18n/sv.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"delete_details": "Detta tar bort inspelningsregeln",
"delete_rule": "Ta bort inspelningsregel",
"description": "Beskrivning",
"download": "Ladda ner inspelning",
"duration": "Varaktighet",
"edit_metadata": "Redigera metadata",
"edit_rule": "Redigera inspelningsregel",
Expand Down Expand Up @@ -216,6 +217,7 @@
"time": "Tid"
},
"videos": {
"download": "Ladda ner video",
"heading": "videoklipp",
"releasedate": "Utgivningsdatum"
}
Expand Down
2 changes: 2 additions & 0 deletions mythtv/html/assets/i18n/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"delete_details": "Bu Kayıt Kuralını siler",
"delete_rule": "Kayıt Kuralını Sil",
"description": "Tanım",
"download": "Kaydı İndir",
"duration": "Süre",
"edit_metadata": "Meta Verileri Düzenle",
"edit_rule": "Kayıt Kuralını Düzenle",
Expand Down Expand Up @@ -216,6 +217,7 @@
"time": "Zaman"
},
"videos": {
"download": "Video indir",
"heading": "videolar",
"releasedate": "Yayın tarihi"
}
Expand Down
2 changes: 2 additions & 0 deletions mythtv/html/assets/i18n/zh_CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"delete_details": "这将删除记录规则",
"delete_rule": "删除录音规则",
"description": "描述",
"download": "下载录音",
"duration": "期间",
"edit_metadata": "编辑元数据",
"edit_rule": "编辑录音规则",
Expand Down Expand Up @@ -216,6 +217,7 @@
"time": "时间"
},
"videos": {
"download": "下载视频",
"heading": "视频",
"releasedate": "发布日期"
}
Expand Down
2 changes: 2 additions & 0 deletions mythtv/html/assets/i18n/zh_HK.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"delete_details": "這將刪除記錄規則",
"delete_rule": "刪除錄音規則",
"description": "描述",
"download": "下載錄音",
"duration": "期間",
"edit_metadata": "編輯元數據",
"edit_rule": "編輯錄音規則",
Expand Down Expand Up @@ -216,6 +217,7 @@
"time": "時間"
},
"videos": {
"download": "下載視頻",
"heading": "影片",
"releasedate": "發布日期"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,24 @@ <h2>{{ 'dashboard.recordings.heading' | translate }}</h2>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th>{{ 'dashboard.recordings.filter' | translate }}: &nbsp;&nbsp; {{ 'dashboard.recordings.title' |
translate }}: &nbsp; <p-columnFilter type="text" field="Title"
[matchModeOptions]="matchModeTitle"></p-columnFilter>
{{ 'dashboard.recordings.recgrp' | translate }}: &nbsp; <p-columnFilter type="text"
[matchModeOptions]="matchModeRecGrp" field="Recording.RecGroup"></p-columnFilter>
<th>
{{ 'dashboard.recordings.filter' | translate }}: &nbsp;&nbsp; {{ 'dashboard.recordings.title' |
translate }}: &nbsp;
<p-columnFilter type="text" field="Title" [matchModeOptions]="matchModeTitle"></p-columnFilter>

{{ 'dashboard.recordings.recgrp' | translate }}: &nbsp;
<p-columnFilter field="Recording.RecGroup" matchMode="equals" [showMenu]="false">
<ng-template pTemplate="filter" let-value let-filter="filterCallback">
<p-dropdown [ngModel]="value" [options]="recGroups" name="filter"
(onChange)="filter($event.value)"
placeholder="{{ 'settings.chanedit.all' | translate }}" [showClear]="true">
<ng-template let-option pTemplate="item">
{{option}}
</ng-template>
</p-dropdown>
</ng-template>
</p-columnFilter>

<button pButton pRipple icon="pi pi-refresh" class="p-button-text .p-button-success"
(click)="refreshing=true;refresh()" pTooltip="{{ 'common.refresh' | translate }}"></button>
&nbsp;&nbsp;&nbsp;{{ programs.length }} Rows
Expand Down Expand Up @@ -50,7 +63,9 @@ <h2>{{ 'dashboard.recordings.heading' | translate }}</h2>
</th>
<th style="flex-basis: 8%" class="justify-content-end p-1">{{
'dashboard.recordings.file_size' | translate }}</th>
<th style="flex-basis: 2%" class="p-1">
<th style="flex-basis: 4%" class="p-1">
<a target="_blank">
<i class="pi pi-download"></i></a>
<button pButton pRipple icon="pi pi-ellipsis-v" class="p-button-text p-button-primary"
[disabled]="true"></button>
</th>
Expand Down Expand Up @@ -83,7 +98,10 @@ <h2>{{ 'dashboard.recordings.heading' | translate }}</h2>
number:'1.0-0' }} min</td>
<td style="flex-basis: 8%" class="justify-content-end p-1">
{{program.Recording.FileSize / 1000000 | number:'1.0-0'}} MB</td>
<td style="flex-basis: 2%" class="p-1">
<td style="flex-basis: 4%" class="p-1">
<a href="{{URLencode('/Content/GetRecording?RecordedId=' + program.Recording.RecordedId)}}"
target="_blank" pTooltip="{{ 'dashboard.recordings.download' | translate }}" tooltipPosition="left">
<i class="pi pi-download"></i></a>
<button pButton pRipple icon="pi pi-ellipsis-v" class="p-button-text p-button-primary"
(click)="showMenu(program,$event)"></button>
</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class RecordingsComponent implements OnInit {

recordings!: ProgramList;
programs: ScheduleOrProgram[] = [];
recGroups: string[] = [];
lazyLoadEvent!: LazyLoadEvent;
JobQCmds!: JobQCommands;
program: ScheduleOrProgram = <ScheduleOrProgram>{ Title: '' };
Expand Down Expand Up @@ -88,12 +89,16 @@ export class RecordingsComponent implements OnInit {
}
];


constructor(private dvrService: DvrService, private messageService: MessageService,
public translate: TranslateService, private setupService: SetupService,
public utility: UtilityService) {
this.JobQCmds = this.setupService.getJobQCommands();

this.dvrService.GetRecGroupList()
.subscribe((data) => {
this.recGroups = data.RecGroupList;
this.recGroups.push('Deleted');
});
// translations
for (const [key, value] of Object.entries(this.msg)) {
this.translate.get(value).subscribe(data => {
Expand Down Expand Up @@ -160,7 +165,9 @@ export class RecordingsComponent implements OnInit {
this.recordings = data.ProgramList;
this.programs.length = data.ProgramList.TotalAvailable;
// populate page of virtual programs
this.programs.splice(request.StartIndex!, request.Count!, ...this.recordings.Programs);
// this.programs.splice(request.StartIndex!, request.Count!, ...this.recordings.Programs);
this.programs.splice(this.recordings.StartIndex, this.recordings.Count,
...this.recordings.Programs);
// notify of change
this.programs = [...this.programs]
this.refreshing = false;
Expand All @@ -171,6 +178,10 @@ export class RecordingsComponent implements OnInit {
this.loadLazy(this.lazyLoadEvent);
}

URLencode(x: string): string {
return encodeURI(x);
}

getDuration(program: ScheduleOrProgram): number {
const starttm = new Date(program.Recording.StartTs).getTime();
const endtm = new Date(program.Recording.EndTs).getTime();
Expand Down
17 changes: 14 additions & 3 deletions mythtv/html/backend/src/app/dashboard/videos/videos.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,20 @@ <h2>{{ 'dashboard.videos.heading' | translate }}</h2>
styleClass="p-button-primary">
</p-button>
</div>
&nbsp;&nbsp;&nbsp;
<p-checkbox inputId="showAllVideos" [(ngModel)]="showAllVideos" name="showAllVideos"
#showAll="ngModel" [binary]="true" (onChange)="showAllChange()"
label="{{ 'dashboard.upcoming.showall' | translate }} ">
</p-checkbox>

<div *ngIf="refreshing else refreshBn"><p-progressSpinner
[style]="{width: '30px', height: '30px'}"></p-progressSpinner></div>
<ng-template #refreshBn>
<button pButton pRipple icon="pi pi-refresh" class="p-button-text .p-button-success"
(click)="refreshing=true;loadVideos()"
pTooltip="{{ 'common.refresh' | translate }}"></button>
</ng-template>

&nbsp;&nbsp;&nbsp;{{ videos.length }} Rows
</th>
</tr>
<tr>
Expand All @@ -45,7 +51,9 @@ <h2>{{ 'dashboard.videos.heading' | translate }}</h2>
<th style="flex-basis: 10%" class="justify-content-end p-1">
{{ 'dashboard.recordings.duration' | translate }}
</th>
<th style="flex-basis: 3%" class="p-1">
<th style="flex-basis: 6%" class="p-1">
<a target="_blank">
<i class="pi pi-download"></i></a>
<button pButton pRipple icon="pi pi-ellipsis-v" class="p-button-text p-button-primary"
[disabled]="true"></button>
</th>
Expand Down Expand Up @@ -77,8 +85,11 @@ <h2>{{ 'dashboard.videos.heading' | translate }}</h2>
<td style="flex-basis: 10%" class="justify-content-end p-1">
<div *ngIf="video.Length > 0"> {{ video.Length }} min </div>
</td>
<td style="flex-basis: 3%" class="p-1">
<td style="flex-basis: 6%" class="p-1">
<div *ngIf="video.ContentType != 'D'">
<a href="{{URLencode('/Content/GetVideo?Id=' + video.Id)}}" target="_blank"
pTooltip="{{ 'dashboard.videos.download' | translate }}" tooltipPosition="left">
<i class="pi pi-download"></i></a>
<button pButton pRipple icon="pi pi-ellipsis-v" class="p-button-text p-button-primary"
(click)="showMenu(video,$event)"></button>
</div>
Expand Down
81 changes: 48 additions & 33 deletions mythtv/html/backend/src/app/dashboard/videos/videos.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { MenuItem, MessageService } from 'primeng/api';
import { LazyLoadEvent, MenuItem, MessageService } from 'primeng/api';
import { Menu } from 'primeng/menu';
import { PartialObserver } from 'rxjs';
import { UpdateVideoMetadataRequest, VideoMetadataInfo } from 'src/app/services/interfaces/video.interface';
Expand Down Expand Up @@ -30,6 +30,7 @@ export class VideosComponent implements OnInit {
editingVideo?: VideoMetadataInfo;
displayMetadataDlg = false;
displayUnsaved = false;
showAllVideos = false;

mnu_markwatched: MenuItem = { label: 'dashboard.recordings.mnu_markwatched', command: (event) => this.markwatched(event, true) };
mnu_markunwatched: MenuItem = { label: 'dashboard.recordings.mnu_markunwatched', command: (event) => this.markwatched(event, false) };
Expand Down Expand Up @@ -72,46 +73,61 @@ export class VideosComponent implements OnInit {
loadVideos() {
this.videoService.GetVideoList({ Sort: "FileName" }).subscribe(data => {
this.allVideos = data.VideoMetadataInfoList.VideoMetadataInfos;
// this.videos = data.VideoMetadataInfoList.VideoMetadataInfos;
this.refreshing = false;
this.filterVideos();
this.loaded = true;
this.refreshing = false;
});
}

showAllChange() {
this.refreshing = true;
setTimeout(() => this.filterVideos(), 100);
}

filterVideos() {
this.videos = [];
let prior = '';
let search = this.directory.join('/');
if (search.length > 0)
search += '/';
this.allVideos.forEach(
video => {
const parts = video.FileName.split('/');
if (video.FileName.startsWith(search)) {
if (parts.length == this.directory.length + 1) {
this.videos.push(video);
prior = '';
}
else {
let dir = parts.slice(0, this.directory.length + 1)
.join('/') + '/';
if (dir != prior) {
// Dummy directory entry
this.videos.push(<VideoMetadataInfo>{
FileName: dir,
Title: parts[this.directory.length],
ContentType: 'D', // indicates directory
Season: 0,
Episode: 0,
Length: 0
});
prior = dir;
if (this.showAllVideos) {
this.directory.length = 0;
this.videos = [...this.allVideos];
}
else {
this.videos = [];
let prior = '';
let search = this.directory.join('/');
if (search.length > 0)
search += '/';
this.allVideos.forEach(
video => {
const parts = video.FileName.split('/');
if (video.FileName.startsWith(search)) {
if (parts.length == this.directory.length + 1) {
this.videos.push(video);
prior = '';
}
else {
let dir = parts.slice(0, this.directory.length + 1)
.join('/') + '/';
if (dir != prior) {
// Dummy directory entry
this.videos.push(<VideoMetadataInfo>{
FileName: dir,
Title: parts[this.directory.length],
ContentType: 'D', // indicates directory
Season: 0,
Episode: 0,
Length: 0
});
prior = dir;
}
}
}
}
}
);
);
}
this.refreshing = false;
}

URLencode(x: string): string {
return encodeURI(x);
}

onDirectory(subdir: string) {
Expand Down Expand Up @@ -170,7 +186,6 @@ export class VideosComponent implements OnInit {
severity: severity, summary: text,
detail: action + ' ' + this.video.Title + ' ' + this.video.SubTitle + extraText,
life: 3000,
// contentStyleClass: 'recsmsg'
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ <h2>{{ 'dashboard.status.encoder_title' | translate }}</h2>
<ng-container *ngIf="encoder.State == 7"> '{{ encoder.Recording.Title }}' on {{
encoder.Recording.Channel.CallSign }}.
{{ 'dashboard.status.endtime_text' |
translate: { EndTime: encoder.Recording.EndTime } }}
translate: { EndTime: utility.formatTime(encoder.Recording.EndTime) } }}
</ng-container>
</li>
<div *ngFor="let input of encoder.Inputs">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, OnInit, Input } from '@angular/core';
import { Encoder, TVState } from 'src/app/services/interfaces/encoder.interface';
import { UtilityService } from 'src/app/services/utility.service';

@Component({
selector: 'app-status-encoders',
Expand All @@ -9,7 +10,7 @@ import { Encoder, TVState } from 'src/app/services/interfaces/encoder.interface'
export class EncodersComponent implements OnInit {
@Input() encoders? : Encoder[];

constructor() { }
constructor(public utility: UtilityService) { }

ngOnInit(): void {
}
Expand Down