implement video thumbnail generation and display in movie banner
This commit is contained in:
parent
6f7c9d149b
commit
20a399b5c7
4 changed files with 73 additions and 13 deletions
|
|
@ -44,7 +44,7 @@ def delete_original_video(source):
|
||||||
|
|
||||||
def create_thumbnails(instance, model_id):
|
def create_thumbnails(instance, model_id):
|
||||||
"""
|
"""
|
||||||
Creates high-resolution and low-resolution thumbnails from a video file
|
Creates high-resolution and low-resolution thumbnails from a video file.
|
||||||
"""
|
"""
|
||||||
video_file_path = instance.video_file.path
|
video_file_path = instance.video_file.path
|
||||||
thumbnail_dir = os.path.join(settings.THUMBNAIL_DIR, str(model_id))
|
thumbnail_dir = os.path.join(settings.THUMBNAIL_DIR, str(model_id))
|
||||||
|
|
@ -59,10 +59,32 @@ def create_thumbnails(instance, model_id):
|
||||||
os.makedirs(thumbnail_dir)
|
os.makedirs(thumbnail_dir)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Generate the thumbnails
|
# Generate the image thumbnails
|
||||||
ffmpeg.input(video_file_path, ss=1).output(thumbnail_1080p_path, vf='scale=1920:-1', vframes=1).run(overwrite_output=True)
|
ffmpeg.input(video_file_path, ss=1).output(thumbnail_1080p_path, vf='scale=1920:-1', vframes=1).run(overwrite_output=True)
|
||||||
ffmpeg.input(video_file_path, ss=1).output(thumbnail_480_path, vf='scale=720:-1', vframes=1).run(overwrite_output=True)
|
ffmpeg.input(video_file_path, ss=1).output(thumbnail_480_path, vf='scale=720:-1', vframes=1).run(overwrite_output=True)
|
||||||
|
|
||||||
|
# Create the 10-second video thumbnail
|
||||||
|
create_video_thumbnail(video_file_path, thumbnail_dir, base_filename)
|
||||||
|
|
||||||
except ffmpeg._run.Error as e:
|
except ffmpeg._run.Error as e:
|
||||||
error_message = e.stderr.decode() if e.stderr else "Unknown ffmpeg error"
|
error_message = e.stderr.decode() if e.stderr else "Unknown ffmpeg error"
|
||||||
print(f"Ein Fehler ist aufgetreten: {error_message}")
|
print(f"An error has occurred: {error_message}")
|
||||||
|
|
||||||
|
def create_video_thumbnail(video_file_path, thumbnail_dir, base_filename):
|
||||||
|
"""
|
||||||
|
Creates a 10-second video thumbnail from a video file without audio.
|
||||||
|
"""
|
||||||
|
video_thumbnail_filename = base_filename + '_video-thumbnail.mp4'
|
||||||
|
video_thumbnail_path = os.path.join(thumbnail_dir, video_thumbnail_filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ffmpeg.input(video_file_path, ss=1, t=10).output(
|
||||||
|
video_thumbnail_path,
|
||||||
|
vf='scale=1280:-1',
|
||||||
|
vcodec='libx264',
|
||||||
|
an=None # Disable audio
|
||||||
|
).run(overwrite_output=True)
|
||||||
|
|
||||||
|
except ffmpeg._run.Error as e:
|
||||||
|
error_message = e.stderr.decode() if e.stderr else "Unknown ffmpeg error"
|
||||||
|
print(f"An error has occurred: {error_message}")
|
||||||
|
|
@ -5,21 +5,39 @@
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="movie-banner"
|
class="movie-banner"
|
||||||
[ngStyle]="{
|
[ngClass]="{
|
||||||
'background-image':
|
fullVhBanner: !screenWidth
|
||||||
'url(' +
|
}"
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
#videoElement
|
||||||
|
*ngIf="currentMovie[0]?.file_name"
|
||||||
|
[src]="
|
||||||
environmentBaseUrl +
|
environmentBaseUrl +
|
||||||
'/media/thumbnails/' +
|
'/media/thumbnails/' +
|
||||||
currentMovie[0]?.id +
|
currentMovie[0]?.id +
|
||||||
'/' +
|
'/' +
|
||||||
currentMovie[0]?.file_name +
|
currentMovie[0]?.file_name +
|
||||||
'_1080p.jpg' +
|
'_video-thumbnail.mp4'
|
||||||
')',
|
"
|
||||||
}"
|
autoplay
|
||||||
[ngClass]="{
|
muted
|
||||||
fullVhBanner: !screenWidth
|
loop
|
||||||
}"
|
playsinline
|
||||||
></div>
|
class="video-banner"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
[src]="
|
||||||
|
environmentBaseUrl +
|
||||||
|
'/media/thumbnails/' +
|
||||||
|
currentMovie[0]?.id +
|
||||||
|
'/' +
|
||||||
|
currentMovie[0]?.file_name +
|
||||||
|
'_1080p.jpg'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
@if (currentMovie.length > 0) {
|
@if (currentMovie.length > 0) {
|
||||||
<div
|
<div
|
||||||
class="content"
|
class="content"
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,12 @@ section {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-banner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 60%;
|
width: 60%;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
OnChanges,
|
OnChanges,
|
||||||
Output,
|
Output,
|
||||||
SimpleChanges,
|
SimpleChanges,
|
||||||
|
ViewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { BtnLargeComponent } from '../../../../shared/components/buttons/btn-large/btn-large.component';
|
import { BtnLargeComponent } from '../../../../shared/components/buttons/btn-large/btn-large.component';
|
||||||
import { MovieService } from '../../../../services/movie.service';
|
import { MovieService } from '../../../../services/movie.service';
|
||||||
|
|
@ -22,6 +23,7 @@ import { UserService } from '../../../../services/user.service';
|
||||||
styleUrl: './hero-banner.component.scss',
|
styleUrl: './hero-banner.component.scss',
|
||||||
})
|
})
|
||||||
export class HeroBannerComponent implements OnChanges {
|
export class HeroBannerComponent implements OnChanges {
|
||||||
|
@ViewChild('videoElement') videoElementRef!: ElementRef<HTMLVideoElement>;
|
||||||
@Input() currentMovie: any[] = [];
|
@Input() currentMovie: any[] = [];
|
||||||
@Input() screenWidth: boolean = false;
|
@Input() screenWidth: boolean = false;
|
||||||
@Input() favoriteMovies: any[] = [];
|
@Input() favoriteMovies: any[] = [];
|
||||||
|
|
@ -44,6 +46,10 @@ export class HeroBannerComponent implements OnChanges {
|
||||||
public userService: UserService
|
public userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.videoSpeed();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes['currentMovie'] && this.currentMovie.length > 0) {
|
if (changes['currentMovie'] && this.currentMovie.length > 0) {
|
||||||
const movieId = this.currentMovie[0]?.id;
|
const movieId = this.currentMovie[0]?.id;
|
||||||
|
|
@ -54,10 +60,18 @@ export class HeroBannerComponent implements OnChanges {
|
||||||
this.movieIsUploaded = resolutions;
|
this.movieIsUploaded = resolutions;
|
||||||
this.movieIsUploadedChange.emit(this.movieIsUploaded);
|
this.movieIsUploadedChange.emit(this.movieIsUploaded);
|
||||||
});
|
});
|
||||||
|
setTimeout(() => this.videoSpeed(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
videoSpeed() {
|
||||||
|
if (this.videoElementRef) {
|
||||||
|
const videoElement = this.videoElementRef.nativeElement;
|
||||||
|
videoElement.playbackRate = 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
toggleLikeMovie(movieId: number): void {
|
toggleLikeMovie(movieId: number): void {
|
||||||
if (this.favoriteMovies.includes(movieId)) {
|
if (this.favoriteMovies.includes(movieId)) {
|
||||||
this.favoriteMovies = this.favoriteMovies.filter((id) => id !== movieId);
|
this.favoriteMovies = this.favoriteMovies.filter((id) => id !== movieId);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue