diff --git a/public/favicon.ico b/public/favicon.ico index 57614f9..a18bbb2 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/src/app/app.component.html b/src/app/app.component.html index ca5ae50..da7989c 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -66,7 +66,7 @@
Downloader
- +
Installed binary @@ -76,7 +76,7 @@ No
- +
Update available
@@ -86,7 +86,7 @@ No - +
- +
Content Decrypter
- +
Installed binary @@ -113,7 +113,7 @@ No
- +
Update available
@@ -123,7 +123,7 @@ No - +
- +
@@ -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);