205 lines
10 KiB
HTML
205 lines
10 KiB
HTML
<div class="row mx-2">
|
|
<div class="col-12 col-lg-8 mt-4">
|
|
<div class="card shadow">
|
|
<div class="card-content p-3 text-center">
|
|
<h1 class="mb-6">CrawlFlix</h1>
|
|
<small>Paramount+ / Max HBO / Netflix / Disney+ / Amazon Prime / Canal+</small>
|
|
</div>
|
|
</div>
|
|
<div class="card shadow mt-3">
|
|
<div class="card-content p-3 text-center">
|
|
<div class="row" *ngIf="welcomeHeader">
|
|
<div class="col-6">
|
|
<b>Downloader</b>
|
|
<div class="row mt-3">
|
|
<div class="col-4">
|
|
Installed binary:
|
|
<span *ngIf="welcomeHeader.downloader.filsIsPresent">
|
|
<i class="fa fa-circle-check text-success me-1"></i>Yes
|
|
</span>
|
|
<span *ngIf="!welcomeHeader.downloader.filsIsPresent">
|
|
<i class="fa fa-exclamation-triangle text-danger me-1"></i>No
|
|
</span>
|
|
</div>
|
|
<div class="col-8">
|
|
Update available:
|
|
<span *ngIf="welcomeHeader.downloader.newReleaseAvailable">
|
|
<i class="fa fa-exclamation-triangle text-danger me-1"></i>Yes -<button [disabled]="welcomeHeader.downloader.isUpdating" (click)="processUpdate(welcomeHeader.downloader, 'downloader')" style="margin-top:-3px" class="btn btn-outline-primary btn-sm ms-2 p-0 px-1"><i *ngIf="welcomeHeader.downloader.isUpdating" class="fa fa-circle-notch fa-spin me-1"></i>Proceed</button>
|
|
</span>
|
|
<span *ngIf="!welcomeHeader.downloader.newReleaseAvailable">
|
|
<i class="fa fa-circle-check text-success me-1"></i>No
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<b>Content Decrypter</b>
|
|
<div class="row mt-3">
|
|
<div class="col-4">
|
|
Installed binary:
|
|
<span *ngIf="welcomeHeader.mp4decrypt.filsIsPresent">
|
|
<i class="fa fa-circle-check text-success me-1"></i>Yes
|
|
</span>
|
|
<span *ngIf="!welcomeHeader.mp4decrypt.filsIsPresent">
|
|
<i class="fa fa-exclamation-triangle text-danger me-1"></i>No
|
|
</span>
|
|
</div>
|
|
<div class="col-8">
|
|
Update available:
|
|
<span *ngIf="welcomeHeader.mp4decrypt.newReleaseAvailable">
|
|
<i class="fa fa-exclamation-triangle text-danger me-1"></i>Yes -<button [disabled]="welcomeHeader.mp4decrypt.isUpdating" (click)="processUpdate(welcomeHeader.mp4decrypt, 'mp4decrypt')" style="margin-top:-3px" class="btn btn-outline-primary btn-sm ms-2 p-0 px-1"><i *ngIf="welcomeHeader.mp4decrypt.isUpdating" class="fa fa-circle-notch fa-spin me-1"></i>Proceed</button>
|
|
</span>
|
|
<span *ngIf="!welcomeHeader.mp4decrypt.newReleaseAvailable">
|
|
<i class="fa fa-circle-check text-success me-1"></i>No
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row p-2" *ngIf="!welcomeHeader">
|
|
<div class="col-12">
|
|
<i class="fa fa-circle-notch fa-2x fa-spin"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card shadow mt-3">
|
|
<div class="card-content p-3">
|
|
<form [formGroup]="loadForm" (ngSubmit)="onSubmitLoad()">
|
|
<div class="flex flex-col mt-2">
|
|
<label>MP4 Filename</label>
|
|
<input type="text" formControlName="mp4Filename" class="form-control" placeholder="ex: S01E01">
|
|
</div>
|
|
<div class="flex flex-col mt-2">
|
|
<label>MPD URL</label>
|
|
<input type="text" formControlName="mpdUrl" class="form-control" placeholder="ex: https://....">
|
|
</div>
|
|
<button type="submit" [disabled]="!loadForm.valid" class="btn btn-primary float-end mt-2"><i class="fa fa-download me-2"></i>Load</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="card shadow mt-3" *ngIf="loaded">
|
|
<div class="card-content p-3">
|
|
<form [formGroup]="processingForm" (ngSubmit)="onSubmit()">
|
|
<div class="row mt-2">
|
|
<div class="col-9">
|
|
<div class="flex flex-col mt-2">
|
|
<label>Wanted Resolution</label>
|
|
<select formControlName="wantedResolution" class="form-select">
|
|
<option [value]="res.name" *ngFor="let res of loaded.videoTracks">{{res.name}} ({{res.codec}} - {{humanFileSize(res.bandwidth)}})</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-3">
|
|
<label class="mb-2">Process remux</label>
|
|
<div class="form-check form-switch">
|
|
<input [ngModelOptions]="{standalone: true}" [(ngModel)]="processRemux" class="form-check-input" type="checkbox" role="switch" id="remuxProcess">
|
|
<label class="form-check-label" for="remuxProcess">
|
|
Process Full MKV Remux
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-2">
|
|
<div class="col-8">
|
|
<label class="mb-2">Wanted Audio Tracks</label>
|
|
<div class="form-check form-switch" *ngFor="let audio of loaded.audioTracks; let index = index">
|
|
<input [ngModelOptions]="{standalone: true}" [(ngModel)]="audio.selected" class="form-check-input" type="checkbox" role="switch" [id]="'audio_track_' + index">
|
|
<label class="form-check-label" [for]="'audio_track_' + index">
|
|
{{audio.name}} ({{audio.language}} - {{audio.attributes.CODECS}} - {{humanFileSize(audio.attributes.BANDWIDTH)}})
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-4" *ngIf="loaded.subtitles && loaded.subtitles.length > 0">
|
|
<label class="mb-2">Wanted Subtitles Tracks</label>
|
|
<div class="form-check form-switch" *ngFor="let sub of loaded.subtitles; let index = index">
|
|
<input [ngModelOptions]="{standalone: true}" [(ngModel)]="sub.selected" class="form-check-input" type="checkbox" role="switch" [id]="'sub_track_' + index">
|
|
<label class="form-check-label" [for]="'sub_track_' + index">
|
|
{{sub.name}} ({{sub.language}} - {{sub.attributes.CODECS}} - {{humanFileSize(sub.attributes.BANDWIDTH)}})
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col mt-2">
|
|
<label>Widevine L3 Decryption Keys (one per line, key:value)</label>
|
|
<textarea formControlName="keys" rows="4" class="form-control" placeholder="ex: ca2c28ab6c6a4e91935e12b6bb37a952:779afc2503a1ea7947b4747d24fbeb63"></textarea>
|
|
</div>
|
|
<button type="submit" [disabled]="!processingForm.valid" class="btn btn-primary float-end mt-2"><i class="fa fa-cog me-2"></i>Add to Queue</button>
|
|
</form>
|
|
|
|
<!-- <div *ngIf="jobId" class="mt-6">
|
|
<h2 class="text-xl font-semibold mb-2">Job Status</h2>
|
|
<p>Job ID: {{ jobId }}</p>
|
|
<p>Status: {{ jobStatus }}</p> ({{jobProgress}}%)
|
|
<div class="w-full bg-gray-200 rounded-full h-2.5 mt-2">
|
|
<div class="bg-blue-600 h-2.5 rounded-full" [style.width.%]="jobProgress"></div>
|
|
</div>
|
|
</div> -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-lg-4 mt-4">
|
|
<div class="card shadow">
|
|
<div class="card-header">
|
|
Jobs Queue ({{jobs.length}})
|
|
<button class="btn btn-clear ms-2 mt-0 p-0 float-end">
|
|
<i (click)="flushQueue()" class="fa fa-trash text-danger fa-fw"></i>
|
|
</button>
|
|
<i [ngClass]="{'fa-beat-fade text-success': lastJobSuccess, 'text-danger': !lastJobSuccess}" class="fa fa-circle float-end mt-1"></i>
|
|
</div>
|
|
<div class="card-content p-2 px-3" style="max-height: 90vh;overflow-y:scroll">
|
|
<div class="row" *ngIf="jobs.length === 0">
|
|
<div class="col-12 text-center py-3">
|
|
<!-- <i class="fa fa-circle-notch fa-spin fa-2x"></i> -->
|
|
<i>Nothing to display</i>
|
|
</div>
|
|
</div>
|
|
<div *ngFor="let job of jobs | orderBy: 'addedOn':true; let last = last" class="pb-2" [ngClass]="{'border-bottom mb-2': !last}">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card my-2 border-0" style="background-color: #f5f6fa;">
|
|
<div class="card-content p-2">
|
|
<div class="row">
|
|
<div class="col-12 text-center mb-2">
|
|
<h5 class="mb-0"><b>{{job.data.mp4Filename}}</b></h5>
|
|
</div>
|
|
<div class="col-12 mb-1">
|
|
<i class="fa fa-display me-2 text-primary"></i><i>{{job.data.wantedResolution?.name || 'Unknown'}} @{{humanFileSize(job.data.wantedResolution.bandwidth)}} ({{job.data.wantedResolution.codec}})</i><br>
|
|
<i class="fa fa-volume-high me-2 text-success"></i><i>{{displayJobAudio(job?.data?.wantedAudioTracks) || 'Unknown'}}</i><br>
|
|
<i class="fa fa-closed-captioning me-2 text-warning"></i><i>{{displayJobAudio(job?.data?.wantedSubtitles) || 'Unknown'}}</i><br>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<p class="text-sm mb-1">Job ID: {{ job.id }}</p>
|
|
</div>
|
|
<div class="col-6 text-end">
|
|
<p class="text-sm mb-1">Status: {{ job.state }}</p>
|
|
</div>
|
|
<div class="col-6">
|
|
<p class="text-sm mb-1">Added {{ job.addedOn | amTimeAgo }}</p>
|
|
</div>
|
|
<div class="col-6 text-end">
|
|
<p class="text-sm mb-1">Finished {{ job.finishedOn | amTimeAgo }}</p>
|
|
</div>
|
|
<div class="col-12">
|
|
<p class="text-sm mb-1">Duration: {{ job.finishedOn ? (((job.finishedOn - job.processedOn)/1000) > 120 ? (((job.finishedOn - job.processedOn)/1000) | amDuration:'seconds') : Math.floor((job.finishedOn - job.processedOn)/1000)) + 's' : 'In progress' }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="alert alert-danger mt-2 mb-1" *ngIf="job.state === 'failed' && job.failedReason">
|
|
{{job.failedReason}}
|
|
</div>
|
|
<div class="alert alert-success mt-2 mb-4" *ngIf="job.state === 'completed' && job.returnValue?.message">
|
|
{{job.returnValue?.message}}<br>
|
|
<button *ngIf="job.returnValue.filePath && job.returnValue.fileName" [disabled]="job.isDownloading" class="btn btn-success btn-sm float-end" (click)="downloadFileFromAPI(job.returnValue.filePath, job.returnValue.fileName, job)"><i *ngIf="!job.isDownloading" class="fa fa-download me-2"></i><i *ngIf="job.isDownloading" class="fa fa-circle-notch fa-spin me-2"></i>Download</button>
|
|
</div>
|
|
<div class="progress" *ngIf="job.state !== 'failed'">
|
|
<div class="progress-bar" [ngClass]="{'bg-warning': job.state === 'waiting', 'bg-success': job.state === 'completed', 'bg-danger': job.state === 'failed'}" role="progressbar" [style.width.%]="job.progress" aria-valuemin="0" aria-valuemax="100">{{job.progress}}%</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div> |