mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-07 12:00:01 +03:00
Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into download-manager
This commit is contained in:
@@ -31,7 +31,8 @@
|
||||
"youtube_API_key": "",
|
||||
"use_twitch_API": false,
|
||||
"twitch_API_key": "",
|
||||
"twitch_auto_download_chat": false
|
||||
"twitch_auto_download_chat": false,
|
||||
"use_sponsorblock_API": false
|
||||
},
|
||||
"Themes": {
|
||||
"default_theme": "default",
|
||||
|
||||
@@ -208,7 +208,8 @@ DEFAULT_CONFIG = {
|
||||
"youtube_API_key": "",
|
||||
"use_twitch_API": false,
|
||||
"twitch_API_key": "",
|
||||
"twitch_auto_download_chat": false
|
||||
"twitch_auto_download_chat": false,
|
||||
"use_sponsorblock_API": false
|
||||
},
|
||||
"Themes": {
|
||||
"default_theme": "default",
|
||||
@@ -217,7 +218,7 @@ DEFAULT_CONFIG = {
|
||||
"Subscriptions": {
|
||||
"allow_subscriptions": true,
|
||||
"subscriptions_base_path": "subscriptions/",
|
||||
"subscriptions_check_interval": "300",
|
||||
"subscriptions_check_interval": "86400",
|
||||
"redownload_fresh_uploads": false,
|
||||
"download_delay": ""
|
||||
},
|
||||
|
||||
@@ -106,6 +106,10 @@ let CONFIG_ITEMS = {
|
||||
'key': 'ytdl_twitch_auto_download_chat',
|
||||
'path': 'YoutubeDLMaterial.API.twitch_auto_download_chat'
|
||||
},
|
||||
'ytdl_use_sponsorblock_api': {
|
||||
'key': 'ytdl_use_sponsorblock_api',
|
||||
'path': 'YoutubeDLMaterial.API.use_sponsorblock_API'
|
||||
},
|
||||
|
||||
// Themes
|
||||
'ytdl_default_theme': {
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -3800,6 +3800,11 @@
|
||||
"randomfill": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"crypto-js": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
|
||||
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
|
||||
},
|
||||
"css": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
|
||||
@@ -13743,7 +13748,8 @@
|
||||
},
|
||||
"ssri": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "",
|
||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
|
||||
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"figgy-pudding": "^3.5.1"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@ngneat/content-loader": "^5.0.0",
|
||||
"@videogular/ngx-videogular": "^2.1.0",
|
||||
"core-js": "^2.4.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"file-saver": "^2.0.2",
|
||||
"filesize": "^6.1.0",
|
||||
"fingerprintjs2": "^2.1.0",
|
||||
|
||||
@@ -87,6 +87,7 @@ import { TwitchChatComponent } from './components/twitch-chat/twitch-chat.compon
|
||||
import { LinkifyPipe, SeeMoreComponent } from './components/see-more/see-more.component';
|
||||
import { H401Interceptor } from './http.interceptor';
|
||||
import { ConcurrentStreamComponent } from './components/concurrent-stream/concurrent-stream.component';
|
||||
import { SkipAdButtonComponent } from './components/skip-ad-button/skip-ad-button.component';
|
||||
|
||||
registerLocaleData(es, 'es');
|
||||
|
||||
@@ -136,7 +137,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
||||
EditCategoryDialogComponent,
|
||||
TwitchChatComponent,
|
||||
SeeMoreComponent,
|
||||
ConcurrentStreamComponent
|
||||
ConcurrentStreamComponent,
|
||||
SkipAdButtonComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<button *ngIf="show_skip_ad_button" (click)="skipAdButtonClicked()" mat-flat-button><ng-container i18n="Skip ad button">Skip ad</ng-container></button>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SkipAdButtonComponent } from './skip-ad-button.component';
|
||||
|
||||
describe('SkipAdButtonComponent', () => {
|
||||
let component: SkipAdButtonComponent;
|
||||
let fixture: ComponentFixture<SkipAdButtonComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ SkipAdButtonComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SkipAdButtonComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
107
src/app/components/skip-ad-button/skip-ad-button.component.ts
Normal file
107
src/app/components/skip-ad-button/skip-ad-button.component.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
@Component({
|
||||
selector: 'app-skip-ad-button',
|
||||
templateUrl: './skip-ad-button.component.html',
|
||||
styleUrls: ['./skip-ad-button.component.scss']
|
||||
})
|
||||
export class SkipAdButtonComponent implements OnInit {
|
||||
|
||||
@Input() current_video = null;
|
||||
@Input() playback_timestamp = null;
|
||||
|
||||
@Output() setPlaybackTimestamp = new EventEmitter<any>();
|
||||
|
||||
sponsor_block_cache = {};
|
||||
show_skip_ad_button = false;
|
||||
|
||||
constructor(private postsService: PostsService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
setInterval(() => this.skipAdButtonCheck(), 500);
|
||||
}
|
||||
|
||||
checkSponsorBlock(video_to_check) {
|
||||
if (!video_to_check) return;
|
||||
|
||||
// check cache, null means it has been checked and confirmed not to exist (limits API calls)
|
||||
if (this.sponsor_block_cache[video_to_check.url] || this.sponsor_block_cache[video_to_check.url] === null) return;
|
||||
|
||||
// sponsor block needs first 4 chars from video ID hash
|
||||
const video_id = this.getVideoIDFromURL(video_to_check.url);
|
||||
const id_hash = this.getVideoIDHashFromURL(video_id);
|
||||
if (!id_hash || id_hash.length < 4) return;
|
||||
const truncated_id_hash = id_hash.substring(0, 4);
|
||||
|
||||
// we couldn't get the data from the cache, let's get it from sponsor block directly
|
||||
|
||||
this.postsService.getSponsorBlockDataForVideo(truncated_id_hash).subscribe(res => {
|
||||
if (res && res['length'] && res['length'] === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const found_data = res['find'](data => data['videoID'] === video_id);
|
||||
if (found_data) {
|
||||
this.sponsor_block_cache[video_to_check.url] = found_data;
|
||||
}
|
||||
}, err => {
|
||||
// likely doesn't exist
|
||||
this.sponsor_block_cache[video_to_check.url] = null;
|
||||
});
|
||||
}
|
||||
|
||||
getVideoIDHashFromURL(video_id) {
|
||||
if (!video_id) return null;
|
||||
return CryptoJS.SHA256(video_id).toString(CryptoJS.enc.Hex);;
|
||||
}
|
||||
|
||||
getVideoIDFromURL(url) {
|
||||
const regex_exp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
||||
const match = url.match(regex_exp);
|
||||
return (match && match[7].length==11) ? match[7] : null;
|
||||
}
|
||||
|
||||
skipAdButtonCheck() {
|
||||
const sponsor_block_data = this.sponsor_block_cache[this.current_video.url];
|
||||
if (!sponsor_block_data && sponsor_block_data !== null) {
|
||||
// we haven't yet tried to get the sponsor block data for the video
|
||||
this.checkSponsorBlock(this.current_video);
|
||||
} else if (!sponsor_block_data) {
|
||||
this.show_skip_ad_button = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.getTimeToSkipTo()) {
|
||||
this.show_skip_ad_button = true;
|
||||
} else {
|
||||
this.show_skip_ad_button = false;
|
||||
}
|
||||
}
|
||||
|
||||
getTimeToSkipTo() {
|
||||
const sponsor_block_data = this.sponsor_block_cache[this.current_video.url];
|
||||
|
||||
if (!sponsor_block_data) return;
|
||||
|
||||
// check if we're in between an ad segment
|
||||
const found_segment = sponsor_block_data['segments'].find(segment_data => this.playback_timestamp > segment_data.segment[0] && this.playback_timestamp < segment_data.segment[1] - 0.5);
|
||||
|
||||
if (found_segment) {
|
||||
return found_segment['segment'][1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
skipAdButtonClicked() {
|
||||
const time_to_skip_to = this.getTimeToSkipTo();
|
||||
if (!time_to_skip_to) return;
|
||||
|
||||
this.setPlaybackTimestamp.emit(time_to_skip_to);
|
||||
|
||||
this.show_skip_ad_button = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -648,7 +648,6 @@ export class MainComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
this.cachedAvailableFormats[url]['formats'] = this.getAudioAndVideoFormats(infos.formats);
|
||||
console.log(this.cachedAvailableFormats[url]['formats']);
|
||||
}, err => {
|
||||
this.errorFormats(url);
|
||||
});
|
||||
@@ -808,8 +807,6 @@ export class MainComponent implements OnInit {
|
||||
const audio_formats: any = {};
|
||||
const video_formats: any = {};
|
||||
|
||||
console.log(formats);
|
||||
|
||||
for (let i = 0; i < formats.length; i++) {
|
||||
const format_obj = {type: null};
|
||||
|
||||
|
||||
@@ -90,3 +90,9 @@
|
||||
margin-right: 12px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.skip-ad-button {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 75px;
|
||||
}
|
||||
@@ -4,8 +4,9 @@
|
||||
<mat-drawer-container style="height: 100%" class="example-container" autosize>
|
||||
<div style="height: fit-content" [ngClass]="(currentItem.type === 'audio/mp3') ? 'audio-col' : 'video-col'">
|
||||
<vg-player style="height: fit-content; max-height: 75vh" (onPlayerReady)="onPlayerReady($event)" [style.background-color]="(currentItem.type === 'audio/mp3') ? postsService.theme.drawer_color : 'black'">
|
||||
<video [ngClass]="(currentItem.type === 'audio/mp3') ? 'audio-styles' : 'video-styles'" #media class="video-player" [vgMedia]="media" [src]="currentItem.src" id="singleVideo" preload="auto" controls>
|
||||
<video [ngClass]="(currentItem.type === 'audio/mp3') ? 'audio-styles' : 'video-styles'" #media class="video-player" [vgMedia]="media" [src]="currentItem.src" id="singleVideo" preload="auto" controls playsinline>
|
||||
</video>
|
||||
<app-skip-ad-button *ngIf="postsService['config']['API']['use_sponsorblock_API'] && api && playlist?.length > 0 && playlist[currentIndex]['type'] === 'video/mp4'" (setPlaybackTimestamp)="setPlaybackTimestamp($event)" [current_video]="playlist[currentIndex]" [playback_timestamp]="api.currentTime" [sponsor_block_cache]="sponsor_block_cache" class="skip-ad-button"></app-skip-ad-button>
|
||||
</vg-player>
|
||||
</div>
|
||||
<div style="height: fit-content; width: 100%; margin-top: 10px;">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, HostListener, EventEmitter, OnDestroy, AfterViewInit, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { Component, OnInit, HostListener, OnDestroy, AfterViewInit, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { VgApiService } from '@videogular/ngx-videogular/core';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
@@ -15,6 +15,7 @@ export interface IMedia {
|
||||
src: string;
|
||||
type: string;
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -133,7 +134,8 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
title: this.name,
|
||||
label: this.name,
|
||||
src: this.url,
|
||||
type: 'video/mp4'
|
||||
type: 'video/mp4',
|
||||
url: this.url
|
||||
}
|
||||
this.playlist.push(imedia);
|
||||
this.currentItem = this.playlist[0];
|
||||
@@ -229,7 +231,8 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
title: file_obj['title'],
|
||||
src: fullLocation,
|
||||
type: mime_type,
|
||||
label: file_obj['title']
|
||||
label: file_obj['title'],
|
||||
url: file_obj['url']
|
||||
}
|
||||
this.playlist.push(mediaObject);
|
||||
}
|
||||
@@ -289,13 +292,6 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.currentItem = item;
|
||||
}
|
||||
|
||||
getFileInfos() {
|
||||
const fileNames = this.getFileNames();
|
||||
this.postsService.getFileInfo(fileNames, this.type, false).subscribe(res => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
getFileNames() {
|
||||
const fileNames = [];
|
||||
for (let i = 0; i < this.playlist.length; i++) {
|
||||
|
||||
@@ -614,6 +614,11 @@ export class PostsService implements CanActivate {
|
||||
this.httpOptions);
|
||||
}
|
||||
|
||||
getSponsorBlockDataForVideo(id_hash) {
|
||||
const sponsor_block_api_path = 'https://sponsor.ajay.app/api/';
|
||||
return this.http.get(sponsor_block_api_path + `skipSegments/${id_hash}`);
|
||||
}
|
||||
|
||||
public openSnackBar(message: string, action: string = '') {
|
||||
this.snackBar.open(message, action, {
|
||||
duration: 2000,
|
||||
|
||||
@@ -266,12 +266,15 @@
|
||||
<div *ngIf="new_config['API']['use_twitch_API']" class="col-12 mt-1">
|
||||
<mat-checkbox color="accent" [(ngModel)]="new_config['API']['twitch_auto_download_chat']"><ng-container i18n="Auto download Twitch Chat setting">Auto-download Twitch Chat</ng-container></mat-checkbox>
|
||||
</div>
|
||||
<div class="col-12 mb-5">
|
||||
<div class="col-12">
|
||||
<mat-form-field class="text-field" color="accent">
|
||||
<input [disabled]="!new_config['API']['use_twitch_API']" [(ngModel)]="new_config['API']['twitch_API_key']" matInput placeholder="Twitch API Key" i18n-placeholder="Twitch API Key setting placeholder" required>
|
||||
<mat-hint><ng-container i18n="Twitch API Key setting hint AKA preamble">Also known as a Client ID.</ng-container> <a target="_blank" href="https://dev.twitch.tv/docs/api/"><ng-container i18n="Twitch API Key setting hint">Generating a key is easy!</ng-container></a></mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12 mt-4 mb-3">
|
||||
<mat-checkbox color="accent" [(ngModel)]="new_config['API']['use_sponsorblock_API']" matTooltip="Enables a button to skip ads when viewing supported videos." i18n-matTooltip="SponsorBlock API tooltip"><ng-container i18n="Use SponsorBlock API setting">Use SponsorBlock API</ng-container></mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
}
|
||||
|
||||
.text-field {
|
||||
min-width: 30%;
|
||||
width: 95%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.checkbox-button {
|
||||
|
||||
Reference in New Issue
Block a user