@@ -166,7 +166,7 @@
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index b262fc1..4b96a26 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -4,6 +4,7 @@ import { ReactiveFormsModule, FormBuilder, FormGroup, Validators, FormsModule }
import { VideoProcessingService } from './video-processing.service';
import { MomentModule } from 'ngx-moment';
import { OrderModule } from 'ngx-order-pipe';
+import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'app-root',
@@ -24,6 +25,9 @@ export class AppComponent implements OnInit {
processRemux: boolean = true;
loaded: any = null;
Math: any = Math;
+
+ // Indicateur pour le préchargement depuis l'extension
+ isPreloadedFromExtension: boolean = false;
initForm() {
this.processingForm = this.fb.group({
@@ -37,7 +41,9 @@ export class AppComponent implements OnInit {
}
constructor(private fb: FormBuilder,
- private videoProcessingService: VideoProcessingService) {
+ private videoProcessingService: VideoProcessingService,
+ private router: Router,
+ private route: ActivatedRoute) {
this.processingForm = this.fb.group({
keys: ['', Validators.required],
wantedResolution: ['1920x1080', Validators.required]
@@ -51,6 +57,117 @@ export class AppComponent implements OnInit {
ngOnInit(): void {
this.startPollingJobsStatus();
this.sayHello();
+ this.checkForExtensionPreload();
+ const keysControl = this.processingForm.get('keys');
+
+ if (keysControl) {
+ keysControl.valueChanges.subscribe(value => {
+ const keys = this.parseKeys(value);
+ const videoTracks = this.loaded?.videoTracks || [];
+ const decryptableResolutions = videoTracks.filter((res: any) => {
+ if (!res.defaultKID || res.defaultKID.length === 0)
+ return false;
+ return keys.find(k => k.key.toLowerCase() === res.defaultKID.toLowerCase());
+ });
+ if (decryptableResolutions.length > 0) {
+ this.processingForm.get('wantedResolution')?.enable();
+ const currentResolution = this.processingForm.get('wantedResolution')?.value;
+ if (!decryptableResolutions.find((res: any) => res.name === currentResolution)) {
+ this.processingForm.get('wantedResolution')?.setValue(decryptableResolutions[0].name);
+ }
+ } else {
+ this.processingForm.get('wantedResolution')?.disable();
+ this.processingForm.get('wantedResolution')?.setValue(null);
+ }
+ });
+ }
+ }
+
+ /**
+ * Vérifie si l'application a été ouverte avec des paramètres depuis l'extension
+ */
+ private checkForExtensionPreload(): void {
+ this.route.queryParams.subscribe(params => {
+ if (params['mpdUrl'] && params['keys'] && params['autoLoad']) {
+ this.preloadFromExtension(params['mpdUrl'], params['keys']);
+ // this.router.navigate([], {
+ // queryParams: {
+ // mpdUrl: null,
+ // keys: null,
+ // autoLoad: null
+ // },
+ // queryParamsHandling: 'merge'
+ // });
+ }
+ });
+ }
+
+ /**
+ * Précharge les données depuis l'extension Widevine
+ */
+ private preloadFromExtension(mpdUrl: string, keys: string): void {
+ console.log('Preloading data from Widevine extension:', { mpdUrl, keys });
+
+ this.isPreloadedFromExtension = true;
+
+ const filename = this.generateFilenameFromMPD(mpdUrl);
+
+ this.loadForm.patchValue({
+ mpdUrl: mpdUrl,
+ mp4Filename: filename
+ });
+
+ this.processingForm.patchValue({
+ keys: keys
+ });
+
+ setTimeout(() => {
+ this.onSubmitLoad();
+ }, 500);
+ }
+
+ /**
+ * Génère un nom de fichier intelligent basé sur l'URL MPD
+ */
+ private generateFilenameFromMPD(mpdUrl: string): string {
+ try {
+ const url = new URL(mpdUrl);
+
+ // Extraire des informations de l'URL
+ let filename = 'content';
+
+ // Patterns spécifiques pour différents CDNs
+ if (url.hostname.includes('canalplus') || url.hostname.includes('paramount')) {
+ // Exemple: paramplus.com_HDCTF601GLFR
+ const pathMatch = url.pathname.match(/([A-Z0-9_]+)\/\d+\//);
+ if (pathMatch) {
+ filename = pathMatch[1].toLowerCase().replace(/_/g, '-');
+ }
+ } else if (url.pathname.includes('/')) {
+ // Utiliser le dernier segment sans extension
+ const segments = url.pathname.split('/').filter(s => s);
+ const lastSegment = segments[segments.length - 1];
+ filename = lastSegment.replace('.mpd', '').replace(/[^a-zA-Z0-9-_]/g, '-');
+ }
+
+ // Ajouter un timestamp pour éviter les collisions
+ const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
+ return `${filename}_${timestamp}`;
+
+ } catch (error) {
+ console.warn('Could not parse MPD URL for filename generation:', error);
+ const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
+ return `widevine_content_${timestamp}`;
+ }
+ }
+
+ private clearURLParams(): void {
+ const url = new URL(window.location.href);
+ url.searchParams.delete('mpdUrl');
+ url.searchParams.delete('keys');
+ url.searchParams.delete('autoLoad');
+
+ window.history.replaceState({}, document.title, url.toString());
}
downloadFileFromAPI(filePath: string, filename: string, job: any) {
@@ -130,16 +247,31 @@ export class AppComponent implements OnInit {
console.log(data);
this.videoProcessingService.startProcess(data).subscribe({
next: (response) => {
- //this.jobId = response.jobId;
+ // Nettoyer les paramètres URL après avoir démarré le processus
+ if (this.isPreloadedFromExtension) {
+ this.clearURLParams();
+ this.isPreloadedFromExtension = false;
+ }
},
error: (error) => {
console.error('Error starting process:', error);
}
});
- //this.initForm();
}
}
+ getFirstDecryptableResolution() {
+ const decryptableResolutions = this.loaded.videoTracks.filter((res: any) => this.isDecryptable(res));
+ return decryptableResolutions.length > 0 ? decryptableResolutions[0].name : null;
+ }
+
+ isDecryptable(res: any) {
+ if (!res.defaultKID || res.defaultKID.length === 0)
+ return false;
+ const keys = this.parseKeys(this.processingForm.value.keys);
+ return keys.find(k => k.key.toLowerCase() === res.defaultKID.toLowerCase());
+ }
+
displayJobAudio(tracks: any) {
if (tracks.length === 0)
return 'None';
@@ -153,9 +285,22 @@ export class AppComponent implements OnInit {
this.videoProcessingService.load(formData).subscribe({
next: (response) => {
- console.log(response);
this.loaded = response;
- //this.jobId = response.jobId;
+
+ if (this.isPreloadedFromExtension) {
+ console.log('MPD loaded successfully from Widevine extension');
+ const firstDecryptable = this.getFirstDecryptableResolution();
+ if (firstDecryptable) {
+ this.processingForm.patchValue({
+ wantedResolution: firstDecryptable
+ });
+ this.processingForm.get('wantedResolution')?.enable();
+ }
+ } else {
+ console.log('MPD loaded successfully', response);
+ // disable formData wantedResolution
+ this.processingForm.get('wantedResolution')?.disable();
+ }
},
error: (error) => {
console.error('Error starting process:', error);
@@ -165,7 +310,6 @@ export class AppComponent implements OnInit {
}
parseKeys(keysString: string): { key: string, value: string }[] {
- console.log('KS =', keysString);
return keysString.split('\n').map(line => line.trim())
.filter(line => line.includes(':'))
.map(line => {
@@ -180,14 +324,10 @@ export class AppComponent implements OnInit {
next: (response) => {
this.lastJobSuccess = true;
this.jobs = response;
- // if (['completed', 'failed'].includes(response.state)) {
- // clearInterval(interval);
- // }
},
error: (error) => {
console.error('Error fetching job status:', error);
this.lastJobSuccess = false;
- //clearInterval(interval);
}
});
}, 1500);