From 11521f231fec5194abf35900aa6daf427cd55920 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 27 Mar 2024 14:00:40 +0100 Subject: [PATCH] Generate small versions of banners too --- .../about-instance.component.ts | 13 +-- .../edit-instance-information.component.ts | 33 +++--- .../my-video-channels.component.ts | 55 ++++------ client/src/app/helpers/utils/channel.ts | 4 +- .../instance-banner-markup.component.ts | 7 +- .../instance-banner.component.ts | 5 +- .../video-channel/video-channel.model.ts | 3 +- .../src/assets/player/shared/common/utils.ts | 45 +------- client/src/assets/player/utils.ts | 101 ------------------ packages/core-utils/src/common/array.ts | 20 ++++ ...er-resized.jpg => banner-resized-1920.jpg} | Bin .../tests/fixtures/banner-resized-600.jpg | Bin 0 -> 12586 bytes .../banner-user-import-resized-1920.jpg | Bin 0 -> 60003 bytes .../banner-user-import-resized-600.jpg | Bin 0 -> 12018 bytes packages/tests/src/api/server/config.ts | 37 ++++--- packages/tests/src/api/users/user-export.ts | 2 +- packages/tests/src/api/users/user-import.ts | 4 +- .../tests/src/api/videos/video-channels.ts | 34 +++--- packages/tests/src/shared/checks.ts | 4 +- packages/tests/src/shared/import-export.ts | 6 +- .../feeds/shared/common-feed-utils.ts | 13 ++- .../controllers/feeds/video-podcast-feeds.ts | 15 ++- server/core/initializers/constants.ts | 10 +- .../shared/object-to-model-attributes.ts | 40 ++----- server/core/lib/actor-image.ts | 14 --- server/core/lib/html/shared/actor-html.ts | 13 ++- .../exporters/actor-exporter.ts | 5 +- server/core/models/actor/actor.ts | 28 ++--- server/core/models/user/user-notification.ts | 5 +- server/core/models/video/video.ts | 8 +- server/scripts/migrations/peertube-4.2.ts | 5 +- 31 files changed, 184 insertions(+), 345 deletions(-) delete mode 100644 client/src/assets/player/utils.ts rename packages/tests/fixtures/{banner-resized.jpg => banner-resized-1920.jpg} (100%) create mode 100644 packages/tests/fixtures/banner-resized-600.jpg create mode 100644 packages/tests/fixtures/banner-user-import-resized-1920.jpg create mode 100644 packages/tests/fixtures/banner-user-import-resized-600.jpg delete mode 100644 server/core/lib/actor-image.ts diff --git a/client/src/app/+about/about-instance/about-instance.component.ts b/client/src/app/+about/about-instance/about-instance.component.ts index 792567bbe..e0a9a361d 100644 --- a/client/src/app/+about/about-instance/about-instance.component.ts +++ b/client/src/app/+about/about-instance/about-instance.component.ts @@ -1,16 +1,17 @@ -import { ViewportScroller, NgIf, NgFor } from '@angular/common' +import { NgFor, NgIf, ViewportScroller } from '@angular/common' import { AfterViewChecked, Component, ElementRef, OnInit, ViewChild } from '@angular/core' import { ActivatedRoute, RouterLink } from '@angular/router' import { Notifier, ServerService } from '@app/core' +import { AboutHTML } from '@app/shared/shared-main/instance/instance.service' +import { maxBy } from '@peertube/peertube-core-utils' import { HTMLServerConfig, ServerStats } from '@peertube/peertube-models' import { copyToClipboard } from '@root-helpers/utils' +import { CustomMarkupContainerComponent } from '../../shared/shared-custom-markup/custom-markup-container.component' +import { InstanceFeaturesTableComponent } from '../../shared/shared-instance/instance-features-table.component' +import { PluginSelectorDirective } from '../../shared/shared-main/plugins/plugin-selector.directive' import { ResolverData } from './about-instance.resolver' import { ContactAdminModalComponent } from './contact-admin-modal.component' import { InstanceStatisticsComponent } from './instance-statistics.component' -import { InstanceFeaturesTableComponent } from '../../shared/shared-instance/instance-features-table.component' -import { PluginSelectorDirective } from '../../shared/shared-main/plugins/plugin-selector.directive' -import { CustomMarkupContainerComponent } from '../../shared/shared-custom-markup/custom-markup-container.component' -import { AboutHTML } from '@app/shared/shared-main/instance/instance.service' @Component({ selector: 'my-about-instance', @@ -82,7 +83,7 @@ export class AboutInstanceComponent implements OnInit, AfterViewChecked { this.shortDescription = about.instance.shortDescription this.instanceBannerUrl = about.instance.banners.length !== 0 - ? about.instance.banners[0].path + ? maxBy(about.instance.banners, 'width').path : undefined this.serverConfig = this.serverService.getHTMLConfig() diff --git a/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.ts index 88f15aa00..4a5360316 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-instance-information.component.ts @@ -1,22 +1,23 @@ -import { SelectOptionsItem } from 'src/types/select-options-item.model' +import { NgClass, NgIf } from '@angular/common' +import { HttpErrorResponse } from '@angular/common/http' import { Component, Input, OnInit } from '@angular/core' import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms' -import { Notifier, ServerService } from '@app/core' -import { HttpErrorResponse } from '@angular/common/http' -import { genericUploadErrorHandler } from '@app/helpers' -import { ActorImage, HTMLServerConfig } from '@peertube/peertube-models' -import { HelpComponent } from '../../../shared/shared-main/misc/help.component' -import { PeerTubeTemplateDirective } from '../../../shared/shared-main/angular/peertube-template.directive' -import { PeertubeCheckboxComponent } from '../../../shared/shared-forms/peertube-checkbox.component' import { RouterLink } from '@angular/router' -import { SelectCheckboxComponent } from '../../../shared/shared-forms/select/select-checkbox.component' -import { MarkdownTextareaComponent } from '../../../shared/shared-forms/markdown-textarea.component' -import { CustomMarkupHelpComponent } from '../../../shared/shared-custom-markup/custom-markup-help.component' -import { NgClass, NgIf } from '@angular/common' -import { ActorBannerEditComponent } from '../../../shared/shared-actor-image-edit/actor-banner-edit.component' -import { ActorAvatarEditComponent } from '../../../shared/shared-actor-image-edit/actor-avatar-edit.component' -import { InstanceService } from '@app/shared/shared-main/instance/instance.service' +import { Notifier, ServerService } from '@app/core' +import { genericUploadErrorHandler } from '@app/helpers' import { CustomMarkupService } from '@app/shared/shared-custom-markup/custom-markup.service' +import { InstanceService } from '@app/shared/shared-main/instance/instance.service' +import { maxBy } from '@peertube/peertube-core-utils' +import { ActorImage, HTMLServerConfig } from '@peertube/peertube-models' +import { SelectOptionsItem } from 'src/types/select-options-item.model' +import { ActorAvatarEditComponent } from '../../../shared/shared-actor-image-edit/actor-avatar-edit.component' +import { ActorBannerEditComponent } from '../../../shared/shared-actor-image-edit/actor-banner-edit.component' +import { CustomMarkupHelpComponent } from '../../../shared/shared-custom-markup/custom-markup-help.component' +import { MarkdownTextareaComponent } from '../../../shared/shared-forms/markdown-textarea.component' +import { PeertubeCheckboxComponent } from '../../../shared/shared-forms/peertube-checkbox.component' +import { SelectCheckboxComponent } from '../../../shared/shared-forms/select/select-checkbox.component' +import { PeerTubeTemplateDirective } from '../../../shared/shared-main/angular/peertube-template.directive' +import { HelpComponent } from '../../../shared/shared-main/misc/help.component' @Component({ selector: 'my-edit-instance-information', @@ -127,7 +128,7 @@ export class EditInstanceInformationComponent implements OnInit { } private updateActorImages () { - this.instanceBannerUrl = this.serverConfig.instance.banners?.[0]?.path + this.instanceBannerUrl = maxBy(this.serverConfig.instance.banners, 'width')?.path this.instanceAvatars = this.serverConfig.instance.avatars } diff --git a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts index 1bc5829dd..a0060864e 100644 --- a/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts +++ b/client/src/app/+my-library/+my-video-channels/my-video-channels.component.ts @@ -1,23 +1,23 @@ -import { ChartData, ChartOptions, TooltipItem, TooltipModel } from 'chart.js' -import { max, maxBy, min, minBy } from 'lodash-es' -import { Subject, first, map, switchMap } from 'rxjs' +import { NgFor, NgIf } from '@angular/common' import { Component } from '@angular/core' -import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifier, ScreenService } from '@app/core' -import { formatICU } from '@app/helpers' -import { NumberFormatterPipe } from '../../shared/shared-main/angular/number-formatter.pipe' -import { ChartModule } from 'primeng/chart' -import { DeferLoadingDirective } from '../../shared/shared-main/angular/defer-loading.directive' -import { DeleteButtonComponent } from '../../shared/shared-main/buttons/delete-button.component' -import { EditButtonComponent } from '../../shared/shared-main/buttons/edit-button.component' -import { ActorAvatarComponent } from '../../shared/shared-actor-image/actor-avatar.component' -import { InfiniteScrollerDirective } from '../../shared/shared-main/angular/infinite-scroller.directive' -import { AdvancedInputFilterComponent } from '../../shared/shared-forms/advanced-input-filter.component' -import { ChannelsSetupMessageComponent } from '../../shared/shared-main/misc/channels-setup-message.component' import { RouterLink } from '@angular/router' -import { NgIf, NgFor } from '@angular/common' -import { GlobalIconComponent } from '../../shared/shared-icons/global-icon.component' +import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, hasMoreItems } from '@app/core' +import { formatICU } from '@app/helpers' import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model' import { VideoChannelService } from '@app/shared/shared-main/video-channel/video-channel.service' +import { maxBy, minBy } from '@peertube/peertube-core-utils' +import { ChartData, ChartOptions, TooltipItem, TooltipModel } from 'chart.js' +import { ChartModule } from 'primeng/chart' +import { Subject, first, map, switchMap } from 'rxjs' +import { ActorAvatarComponent } from '../../shared/shared-actor-image/actor-avatar.component' +import { AdvancedInputFilterComponent } from '../../shared/shared-forms/advanced-input-filter.component' +import { GlobalIconComponent } from '../../shared/shared-icons/global-icon.component' +import { DeferLoadingDirective } from '../../shared/shared-main/angular/defer-loading.directive' +import { InfiniteScrollerDirective } from '../../shared/shared-main/angular/infinite-scroller.directive' +import { NumberFormatterPipe } from '../../shared/shared-main/angular/number-formatter.pipe' +import { DeleteButtonComponent } from '../../shared/shared-main/buttons/delete-button.component' +import { EditButtonComponent } from '../../shared/shared-main/buttons/edit-button.component' +import { ChannelsSetupMessageComponent } from '../../shared/shared-main/misc/channels-setup-message.component' @Component({ templateUrl: './my-video-channels.component.html', @@ -156,23 +156,8 @@ export class MyVideoChannelsComponent { } private buildChartOptions () { - // chart options that depend on chart data: - // we don't want to skew values and have min at 0, so we define what the floor/ceiling is here - const videoChannelsMinimumDailyViews = min( - // compute local minimum daily views for each channel, by their "views" attribute - this.videoChannels.map(v => minBy( - v.viewsPerDay, - day => day.views - ).views) // the object returned is a ViewPerDate, so we still need to get the views attribute - ) - - const videoChannelsMaximumDailyViews = max( - // compute local maximum daily views for each channel, by their "views" attribute - this.videoChannels.map(v => maxBy( - v.viewsPerDay, - day => day.views - ).views) // the object returned is a ViewPerDate, so we still need to get the views attribute - ) + const channelsMinimumDailyViews = Math.min(...this.videoChannels.map(v => minBy(v.viewsPerDay, 'views').views)) + const channelsMaximumDailyViews = Math.max(...this.videoChannels.map(v => maxBy(v.viewsPerDay, 'views').views)) this.chartOptions = { plugins: { @@ -199,8 +184,8 @@ export class MyVideoChannelsComponent { }, y: { display: false, - min: Math.max(0, videoChannelsMinimumDailyViews - (3 * videoChannelsMaximumDailyViews / 100)), - max: Math.max(1, videoChannelsMaximumDailyViews) + min: Math.max(0, channelsMinimumDailyViews - (3 * channelsMaximumDailyViews / 100)), + max: Math.max(1, channelsMaximumDailyViews) } }, layout: { diff --git a/client/src/app/helpers/utils/channel.ts b/client/src/app/helpers/utils/channel.ts index e7d0c39f7..5ee0d9b03 100644 --- a/client/src/app/helpers/utils/channel.ts +++ b/client/src/app/helpers/utils/channel.ts @@ -1,7 +1,7 @@ -import { minBy } from 'lodash-es' +import { minBy } from '@peertube/peertube-core-utils' +import { VideoChannel } from '@peertube/peertube-models' import { first, map } from 'rxjs/operators' import { SelectChannelItem } from 'src/types/select-options-item.model' -import { VideoChannel } from '@peertube/peertube-models' import { AuthService } from '../../core/auth' function listUserChannelsForSelect (authService: AuthService) { diff --git a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/instance-banner-markup.component.ts b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/instance-banner-markup.component.ts index bb7b62243..9b48e3b42 100644 --- a/client/src/app/shared/shared-custom-markup/peertube-custom-tags/instance-banner-markup.component.ts +++ b/client/src/app/shared/shared-custom-markup/peertube-custom-tags/instance-banner-markup.component.ts @@ -1,7 +1,8 @@ +import { NgClass, NgIf } from '@angular/common' import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core' -import { CustomMarkupComponent } from './shared' import { ServerService } from '@app/core' -import { NgIf, NgClass } from '@angular/common' +import { maxBy } from '@peertube/peertube-core-utils' +import { CustomMarkupComponent } from './shared' /* * Markup component that creates the img HTML element containing the instance banner @@ -28,7 +29,7 @@ export class InstanceBannerMarkupComponent implements OnInit, CustomMarkupCompon ngOnInit () { const { instance } = this.server.getHTMLConfig() - this.instanceBannerUrl = instance.banners?.[0]?.path + this.instanceBannerUrl = maxBy(instance.banners, 'width')?.path this.cd.markForCheck() } } diff --git a/client/src/app/shared/shared-instance/instance-banner.component.ts b/client/src/app/shared/shared-instance/instance-banner.component.ts index b6bc720d0..fa94836a3 100644 --- a/client/src/app/shared/shared-instance/instance-banner.component.ts +++ b/client/src/app/shared/shared-instance/instance-banner.component.ts @@ -1,6 +1,7 @@ +import { NgClass, NgIf } from '@angular/common' import { Component, Input, OnInit, booleanAttribute } from '@angular/core' import { ServerService } from '@app/core' -import { NgIf, NgClass } from '@angular/common' +import { maxBy } from '@peertube/peertube-core-utils' @Component({ selector: 'my-instance-banner', @@ -20,6 +21,6 @@ export class InstanceBannerComponent implements OnInit { ngOnInit () { const { instance } = this.server.getHTMLConfig() - this.instanceBannerUrl = instance.banners?.[0]?.path + this.instanceBannerUrl = maxBy(instance.banners, 'width')?.path } } diff --git a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts index f141d553a..4cbb5ac95 100644 --- a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts +++ b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts @@ -1,6 +1,7 @@ import { getAbsoluteAPIUrl } from '@app/helpers' import { Account as ServerAccount, ActorImage, VideoChannel as ServerVideoChannel, ViewsPerDate } from '@peertube/peertube-models' import { Actor } from '../account/actor.model' +import { maxBy } from '@peertube/peertube-core-utils' export class VideoChannel extends Actor implements ServerVideoChannel { displayName: string @@ -35,7 +36,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel { return '' } - const banner = channel.banners[0] + const banner = maxBy(channel.banners, 'width') if (!banner) return '' if (banner.url) return banner.url diff --git a/client/src/assets/player/shared/common/utils.ts b/client/src/assets/player/shared/common/utils.ts index 4a7182021..80c784ea3 100644 --- a/client/src/assets/player/shared/common/utils.ts +++ b/client/src/assets/player/shared/common/utils.ts @@ -1,6 +1,4 @@ -import { VideoFile } from '@peertube/peertube-models' - -function toTitleCase (str: string) { +export function toTitleCase (str: string) { return str.charAt(0).toUpperCase() + str.slice(1) } @@ -10,36 +8,14 @@ const dictionaryBytes = [ { max: 1073741824, type: 'MB', decimals: 0 }, { max: 1.0995116e12, type: 'GB', decimals: 1 } ] -function bytes (value: number) { +export function bytes (value: number) { const format = dictionaryBytes.find(d => value < d.max) || dictionaryBytes[dictionaryBytes.length - 1] const calc = (value / (format.max / 1024)).toFixed(format.decimals) return [ calc, format.type ] } -function videoFileMaxByResolution (files: VideoFile[]) { - let max = files[0] - - for (let i = 1; i < files.length; i++) { - const file = files[i] - if (max.resolution.id < file.resolution.id) max = file - } - - return max -} - -function videoFileMinByResolution (files: VideoFile[]) { - let min = files[0] - - for (let i = 1; i < files.length; i++) { - const file = files[i] - if (min.resolution.id > file.resolution.id) min = file - } - - return min -} - -function getRtcConfig () { +export function getRtcConfig () { return { iceServers: [ { @@ -52,19 +28,6 @@ function getRtcConfig () { } } -function isSameOrigin (current: string, target: string) { +export function isSameOrigin (current: string, target: string) { return new URL(current).origin === new URL(target).origin } - -// --------------------------------------------------------------------------- - -export { - getRtcConfig, - toTitleCase, - - videoFileMaxByResolution, - videoFileMinByResolution, - bytes, - - isSameOrigin -} diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts deleted file mode 100644 index 1926d45c0..000000000 --- a/client/src/assets/player/utils.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { HTMLServerConfig, Video, VideoFile } from '@peertube/peertube-models' - -function toTitleCase (str: string) { - return str.charAt(0).toUpperCase() + str.slice(1) -} - -function isWebRTCDisabled () { - return !!((window as any).RTCPeerConnection || (window as any).mozRTCPeerConnection || (window as any).webkitRTCPeerConnection) === false -} - -function isP2PEnabled (video: Video, config: HTMLServerConfig, userP2PEnabled: boolean) { - if (video.isLocal && config.tracker.enabled === false) return false - if (isWebRTCDisabled()) return false - - return userP2PEnabled -} - -function isIOS () { - if (/iPad|iPhone|iPod/.test(navigator.platform)) { - return true - } - - // Detect iPad Desktop mode - return !!(navigator.maxTouchPoints && - navigator.maxTouchPoints > 2 && - navigator.platform.includes('MacIntel')) -} - -function isSafari () { - return /^((?!chrome|android).)*safari/i.test(navigator.userAgent) -} - -// https://github.com/danrevah/ngx-pipes/blob/master/src/pipes/math/bytes.ts -// Don't import all Angular stuff, just copy the code with shame -const dictionaryBytes: { max: number, type: string }[] = [ - { max: 1024, type: 'B' }, - { max: 1048576, type: 'KB' }, - { max: 1073741824, type: 'MB' }, - { max: 1.0995116e12, type: 'GB' } -] -function bytes (value: number) { - const format = dictionaryBytes.find(d => value < d.max) || dictionaryBytes[dictionaryBytes.length - 1] - const calc = Math.floor(value / (format.max / 1024)).toString() - - return [ calc, format.type ] -} - -function isMobile () { - return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) -} - -function videoFileMaxByResolution (files: VideoFile[]) { - let max = files[0] - - for (let i = 1; i < files.length; i++) { - const file = files[i] - if (max.resolution.id < file.resolution.id) max = file - } - - return max -} - -function videoFileMinByResolution (files: VideoFile[]) { - let min = files[0] - - for (let i = 1; i < files.length; i++) { - const file = files[i] - if (min.resolution.id > file.resolution.id) min = file - } - - return min -} - -function getRtcConfig () { - return { - iceServers: [ - { - urls: 'stun:stun.stunprotocol.org' - }, - { - urls: 'stun:stun.framasoft.org' - } - ] - } -} - -// --------------------------------------------------------------------------- - -export { - getRtcConfig, - toTitleCase, - isWebRTCDisabled, - isP2PEnabled, - - videoFileMaxByResolution, - videoFileMinByResolution, - isMobile, - bytes, - isIOS, - isSafari -} diff --git a/packages/core-utils/src/common/array.ts b/packages/core-utils/src/common/array.ts index 3978ddd16..f03d4e13c 100644 --- a/packages/core-utils/src/common/array.ts +++ b/packages/core-utils/src/common/array.ts @@ -43,3 +43,23 @@ export function sortBy (obj: any[], key1: string, key2?: string) { return 1 }) } + +export function maxBy (arr: T[], property: keyof T) { + let result: T + + for (const obj of arr) { + if (!result || result[property] < obj[property]) result = obj + } + + return result +} + +export function minBy (arr: T[], property: keyof T) { + let result: T + + for (const obj of arr) { + if (!result || result[property] > obj[property]) result = obj + } + + return result +} diff --git a/packages/tests/fixtures/banner-resized.jpg b/packages/tests/fixtures/banner-resized-1920.jpg similarity index 100% rename from packages/tests/fixtures/banner-resized.jpg rename to packages/tests/fixtures/banner-resized-1920.jpg diff --git a/packages/tests/fixtures/banner-resized-600.jpg b/packages/tests/fixtures/banner-resized-600.jpg new file mode 100644 index 0000000000000000000000000000000000000000..744f2c860dd86ce09e1234e4a2dbe4c24abb9206 GIT binary patch literal 12586 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 z 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 literal 0 HcmV?d00001 diff --git a/packages/tests/fixtures/banner-user-import-resized-600.jpg b/packages/tests/fixtures/banner-user-import-resized-600.jpg new file mode 100644 index 0000000000000000000000000000000000000000..287b803d65387b80f6b3cf574dfb130bbd04b1b7 GIT binary patch literal 12018 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 a.url) for (const url of urls) { diff --git a/packages/tests/src/api/users/user-import.ts b/packages/tests/src/api/users/user-import.ts index 4416cd1b2..7eb93d6d7 100644 --- a/packages/tests/src/api/users/user-import.ts +++ b/packages/tests/src/api/users/user-import.ts @@ -198,7 +198,9 @@ function runTest (withObjectStorage: boolean) { expect(importedSecond.description).to.equal('noah description') expect(importedSecond.support).to.equal('noah support') - await testImage(remoteServer.url, 'banner-resized', importedSecond.banners[0].path) + for (const banner of importedSecond.banners) { + await testImage(remoteServer.url, `banner-user-import-resized-${banner.width}`, banner.path) + } for (const avatar of importedSecond.avatars) { await testImage(remoteServer.url, `avatar-resized-${avatar.width}x${avatar.width}`, avatar.path, '.png') diff --git a/packages/tests/src/api/videos/video-channels.ts b/packages/tests/src/api/videos/video-channels.ts index e43c42e04..34c2563e1 100644 --- a/packages/tests/src/api/videos/video-channels.ts +++ b/packages/tests/src/api/videos/video-channels.ts @@ -1,22 +1,22 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ -import { expect } from 'chai' -import { basename } from 'path' -import { ACTOR_IMAGES_SIZE } from '@peertube/peertube-server/core/initializers/constants.js' -import { testFileExistsOrNot, testImage } from '@tests/shared/checks.js' -import { SQLCommand } from '@tests/shared/sql-command.js' import { wait } from '@peertube/peertube-core-utils' import { ActorImageType, User, VideoChannel } from '@peertube/peertube-models' import { + PeerTubeServer, cleanupTests, createMultipleServers, doubleFollow, - PeerTubeServer, setAccessTokensToServers, setDefaultAccountAvatar, setDefaultVideoChannel, waitJobs } from '@peertube/peertube-server-commands' +import { ACTOR_IMAGES_SIZE } from '@peertube/peertube-server/core/initializers/constants.js' +import { testFileExistsOnFSOrNot, testImage } from '@tests/shared/checks.js' +import { SQLCommand } from '@tests/shared/sql-command.js' +import { expect } from 'chai' +import { basename } from 'path' async function findChannel (server: PeerTubeServer, channelId: number) { const body = await server.channels.list({ sort: '-name' }) @@ -294,7 +294,7 @@ describe('Test video channels', function () { for (const avatar of videoChannel.avatars) { avatarPaths[server.port] = avatar.path await testImage(server.url, `avatar-resized-${avatar.width}x${avatar.width}`, avatarPaths[server.port], '.png') - await testFileExistsOrNot(server, 'avatars', basename(avatarPaths[server.port]), true) + await testFileExistsOnFSOrNot(server, 'avatars', basename(avatarPaths[server.port]), true) const row = await sqlCommands[i].getActorImage(basename(avatarPaths[server.port])) @@ -320,14 +320,18 @@ describe('Test video channels', function () { const server = servers[i] const videoChannel = await server.channels.get({ channelName: 'second_video_channel@' + servers[0].host }) + const expectedSizes = ACTOR_IMAGES_SIZE[ActorImageType.BANNER] - bannerPaths[server.port] = videoChannel.banners[0].path - await testImage(server.url, 'banner-resized', bannerPaths[server.port]) - await testFileExistsOrNot(server, 'avatars', basename(bannerPaths[server.port]), true) + expect(videoChannel.banners.length).to.equal(expectedSizes.length, 'Expected banners to be generated in all sizes') - const row = await sqlCommands[i].getActorImage(basename(bannerPaths[server.port])) - expect(row.height).to.equal(ACTOR_IMAGES_SIZE[ActorImageType.BANNER][0].height) - expect(row.width).to.equal(ACTOR_IMAGES_SIZE[ActorImageType.BANNER][0].width) + for (const banner of videoChannel.banners) { + bannerPaths[server.port] = banner.path + await testImage(server.url, `banner-resized-${banner.width}`, bannerPaths[server.port]) + await testFileExistsOnFSOrNot(server, 'avatars', basename(bannerPaths[server.port]), true) + + const row = await sqlCommands[i].getActorImage(basename(bannerPaths[server.port])) + expect(expectedSizes.some(({ height, width }) => row.height === height && row.width === width)).to.equal(true) + } } }) @@ -357,7 +361,7 @@ describe('Test video channels', function () { for (const server of servers) { const videoChannel = await findChannel(server, secondVideoChannelId) - await testFileExistsOrNot(server, 'avatars', basename(avatarPaths[server.port]), false) + await testFileExistsOnFSOrNot(server, 'avatars', basename(avatarPaths[server.port]), false) expect(videoChannel.avatars).to.be.empty } @@ -372,7 +376,7 @@ describe('Test video channels', function () { for (const server of servers) { const videoChannel = await findChannel(server, secondVideoChannelId) - await testFileExistsOrNot(server, 'avatars', basename(bannerPaths[server.port]), false) + await testFileExistsOnFSOrNot(server, 'avatars', basename(bannerPaths[server.port]), false) expect(videoChannel.banners).to.be.empty } diff --git a/packages/tests/src/shared/checks.ts b/packages/tests/src/shared/checks.ts index 365d02e25..1c3bff2a5 100644 --- a/packages/tests/src/shared/checks.ts +++ b/packages/tests/src/shared/checks.ts @@ -114,7 +114,7 @@ async function testImage (url: string, imageName: string, imageHTTPPath: string, } } -async function testFileExistsOrNot (server: PeerTubeServer, directory: string, filePath: string, exist: boolean) { +async function testFileExistsOnFSOrNot (server: PeerTubeServer, directory: string, filePath: string, exist: boolean) { const base = server.servers.buildDirectory(directory) expect(await pathExists(join(base, filePath))).to.equal(exist) @@ -182,7 +182,7 @@ export { testAvatarSize, testImage, expectLogDoesNotContain, - testFileExistsOrNot, + testFileExistsOnFSOrNot, expectStartWith, expectNotStartWith, expectEndWith, diff --git a/packages/tests/src/shared/import-export.ts b/packages/tests/src/shared/import-export.ts index e973c867a..c9377f7b9 100644 --- a/packages/tests/src/shared/import-export.ts +++ b/packages/tests/src/shared/import-export.ts @@ -27,7 +27,7 @@ import { resolve } from 'path' import { MockSmtpServer } from './mock-servers/mock-email.js' import { getAllNotificationsSettings } from './notifications.js' import { getFilenameFromUrl } from '@peertube/peertube-node-utils' -import { testFileExistsOrNot } from './checks.js' +import { testFileExistsOnFSOrNot } from './checks.js' type ExportOutbox = ActivityPubOrderedCollection> @@ -101,10 +101,10 @@ export async function checkExportFileExists (options: { return makeRawRequest({ url: redirectedUrl, expectedStatus: HttpStatusCode.OK_200 }) } - return testFileExistsOrNot(server, 'tmp-persistent', filename, true) + return testFileExistsOnFSOrNot(server, 'tmp-persistent', filename, true) } - await testFileExistsOrNot(server, 'tmp-persistent', filename, false) + await testFileExistsOnFSOrNot(server, 'tmp-persistent', filename, false) if (withObjectStorage) { await makeRawRequest({ url: redirectedUrl, expectedStatus: HttpStatusCode.NOT_FOUND_404 }) diff --git a/server/core/controllers/feeds/shared/common-feed-utils.ts b/server/core/controllers/feeds/shared/common-feed-utils.ts index af9cafb5d..97d8f575a 100644 --- a/server/core/controllers/feeds/shared/common-feed-utils.ts +++ b/server/core/controllers/feeds/shared/common-feed-utils.ts @@ -1,14 +1,13 @@ -import express from 'express' import { Feed } from '@peertube/feed' import { CustomTag, CustomXMLNS, Person } from '@peertube/feed/lib/typings/index.js' +import { maxBy, pick } from '@peertube/peertube-core-utils' +import { ActorImageType } from '@peertube/peertube-models' import { mdToOneLinePlainText } from '@server/helpers/markdown.js' import { CONFIG } from '@server/initializers/config.js' import { WEBSERVER } from '@server/initializers/constants.js' -import { getBiggestActorImage } from '@server/lib/actor-image.js' import { UserModel } from '@server/models/user/user.js' import { MAccountDefault, MChannelBannerAccountDefault, MUser, MVideoFullLight } from '@server/types/models/index.js' -import { pick } from '@peertube/peertube-core-utils' -import { ActorImageType } from '@peertube/peertube-models' +import express from 'express' export function initFeed (parameters: { name: string @@ -105,12 +104,12 @@ export async function buildFeedMetadata (options: { accountLink = videoChannel.Account.getClientUrl() if (videoChannel.Actor.hasImage(ActorImageType.AVATAR)) { - const videoChannelAvatar = getBiggestActorImage(videoChannel.Actor.Avatars) + const videoChannelAvatar = maxBy(videoChannel.Actor.Avatars, 'width') imageUrl = WEBSERVER.URL + videoChannelAvatar.getStaticPath() } if (videoChannel.Account.Actor.hasImage(ActorImageType.AVATAR)) { - const accountAvatar = getBiggestActorImage(videoChannel.Account.Actor.Avatars) + const accountAvatar = maxBy(videoChannel.Account.Actor.Avatars, 'width') accountImageUrl = WEBSERVER.URL + accountAvatar.getStaticPath() } @@ -123,7 +122,7 @@ export async function buildFeedMetadata (options: { accountLink = link if (account.Actor.hasImage(ActorImageType.AVATAR)) { - const accountAvatar = getBiggestActorImage(account.Actor.Avatars) + const accountAvatar = maxBy(account.Actor.Avatars, 'width') imageUrl = WEBSERVER.URL + accountAvatar?.getStaticPath() accountImageUrl = imageUrl } diff --git a/server/core/controllers/feeds/video-podcast-feeds.ts b/server/core/controllers/feeds/video-podcast-feeds.ts index ad694affa..7e0dca265 100644 --- a/server/core/controllers/feeds/video-podcast-feeds.ts +++ b/server/core/controllers/feeds/video-podcast-feeds.ts @@ -1,21 +1,20 @@ -import express from 'express' -import { extname } from 'path' import { Feed } from '@peertube/feed' import { CustomTag, CustomXMLNS, LiveItemStatus } from '@peertube/feed/lib/typings/index.js' -import { getBiggestActorImage } from '@server/lib/actor-image.js' +import { maxBy, sortObjectComparator } from '@peertube/peertube-core-utils' +import { ActorImageType, VideoFile, VideoInclude, VideoResolution, VideoState } from '@peertube/peertube-models' import { InternalEventEmitter } from '@server/lib/internal-event-emitter.js' import { Hooks } from '@server/lib/plugins/hooks.js' +import { getVideoFileMimeType } from '@server/lib/video-file.js' import { buildPodcastGroupsCache, cacheRouteFactory, videoFeedsPodcastSetCacheKey } from '@server/middlewares/index.js' import { MVideo, MVideoCaptionVideo, MVideoFullLight } from '@server/types/models/index.js' -import { sortObjectComparator } from '@peertube/peertube-core-utils' -import { ActorImageType, VideoFile, VideoInclude, VideoResolution, VideoState } from '@peertube/peertube-models' +import express from 'express' +import { extname } from 'path' import { buildNSFWFilter } from '../../helpers/express-utils.js' import { MIMETYPES, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants.js' import { asyncMiddleware, setFeedPodcastContentType, videoFeedsPodcastValidator } from '../../middlewares/index.js' -import { VideoModel } from '../../models/video/video.js' import { VideoCaptionModel } from '../../models/video/video-caption.js' +import { VideoModel } from '../../models/video/video.js' import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed } from './shared/index.js' -import { getVideoFileMimeType } from '@server/lib/video-file.js' const videoPodcastFeedsRouter = express.Router() @@ -151,7 +150,7 @@ async function generatePodcastItem (options: { let personImage: string if (account.Actor.hasImage(ActorImageType.AVATAR)) { - const avatar = getBiggestActorImage(account.Actor.Avatars) + const avatar = maxBy(account.Actor.Avatars, 'width') personImage = WEBSERVER.URL + avatar.getStaticPath() } diff --git a/server/core/initializers/constants.ts b/server/core/initializers/constants.ts index bac1c2d5b..b8ac94f6f 100644 --- a/server/core/initializers/constants.ts +++ b/server/core/initializers/constants.ts @@ -898,7 +898,7 @@ const PREVIEWS_SIZE = { minWidth: 400 } const ACTOR_IMAGES_SIZE: { [key in ActorImageType_Type]: { width: number, height: number }[] } = { - [ActorImageType.AVATAR]: [ + [ActorImageType.AVATAR]: [ // 1/1 ratio { width: 1500, height: 1500 @@ -916,10 +916,14 @@ const ACTOR_IMAGES_SIZE: { [key in ActorImageType_Type]: { width: number, height height: 48 } ], - [ActorImageType.BANNER]: [ + [ActorImageType.BANNER]: [ // 6/1 ratio { width: 1920, - height: 317 // 6/1 ratio + height: 317 + }, + { + width: 600, + height: 100 } ] } diff --git a/server/core/lib/activitypub/videos/shared/object-to-model-attributes.ts b/server/core/lib/activitypub/videos/shared/object-to-model-attributes.ts index 71846172e..f32fe9f55 100644 --- a/server/core/lib/activitypub/videos/shared/object-to-model-attributes.ts +++ b/server/core/lib/activitypub/videos/shared/object-to-model-attributes.ts @@ -1,4 +1,4 @@ -import { arrayify } from '@peertube/peertube-core-utils' +import { arrayify, maxBy, minBy } from '@peertube/peertube-core-utils' import { ActivityHashTagObject, ActivityMagnetUrlObject, @@ -24,13 +24,11 @@ import { VideoFileModel } from '@server/models/video/video-file.js' import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist.js' import { FilteredModelAttributes } from '@server/types/index.js' import { MChannelId, MStreamingPlaylistVideo, MVideo, MVideoId, isStreamingPlaylist } from '@server/types/models/index.js' -import maxBy from 'lodash-es/maxBy.js' -import minBy from 'lodash-es/minBy.js' import { decode as magnetUriDecode } from 'magnet-uri' import { basename, extname } from 'path' import { getDurationFromActivityStream } from '../../activity.js' -function getThumbnailFromIcons (videoObject: VideoObject) { +export function getThumbnailFromIcons (videoObject: VideoObject) { let validIcons = videoObject.icon.filter(i => i.width > THUMBNAILS_SIZE.minWidth) // Fallback if there are not valid icons if (validIcons.length === 0) validIcons = videoObject.icon @@ -38,19 +36,19 @@ function getThumbnailFromIcons (videoObject: VideoObject) { return minBy(validIcons, 'width') } -function getPreviewFromIcons (videoObject: VideoObject) { +export function getPreviewFromIcons (videoObject: VideoObject) { const validIcons = videoObject.icon.filter(i => i.width > PREVIEWS_SIZE.minWidth) return maxBy(validIcons, 'width') } -function getTagsFromObject (videoObject: VideoObject) { +export function getTagsFromObject (videoObject: VideoObject) { return videoObject.tag .filter(isAPHashTagObject) .map(t => t.name) } -function getFileAttributesFromUrl ( +export function getFileAttributesFromUrl ( videoOrPlaylist: MVideo | MStreamingPlaylistVideo, urls: (ActivityTagObject | ActivityUrlObject)[] ) { @@ -117,7 +115,7 @@ function getFileAttributesFromUrl ( return attributes } -function getStreamingPlaylistAttributesFromObject (video: MVideoId, videoObject: VideoObject) { +export function getStreamingPlaylistAttributesFromObject (video: MVideoId, videoObject: VideoObject) { const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[] if (playlistUrls.length === 0) return [] @@ -154,7 +152,7 @@ function getStreamingPlaylistAttributesFromObject (video: MVideoId, videoObject: return attributes } -function getLiveAttributesFromObject (video: MVideoId, videoObject: VideoObject) { +export function getLiveAttributesFromObject (video: MVideoId, videoObject: VideoObject) { return { saveReplay: videoObject.liveSaveReplay, permanentLive: videoObject.permanentLive, @@ -163,7 +161,7 @@ function getLiveAttributesFromObject (video: MVideoId, videoObject: VideoObject) } } -function getCaptionAttributesFromObject (video: MVideoId, videoObject: VideoObject) { +export function getCaptionAttributesFromObject (video: MVideoId, videoObject: VideoObject) { return videoObject.subtitleLanguage.map(c => ({ videoId: video.id, filename: VideoCaptionModel.generateCaptionName(c.identifier), @@ -172,7 +170,7 @@ function getCaptionAttributesFromObject (video: MVideoId, videoObject: VideoObje })) } -function getStoryboardAttributeFromObject (video: MVideoId, videoObject: VideoObject) { +export function getStoryboardAttributeFromObject (video: MVideoId, videoObject: VideoObject) { if (!isArray(videoObject.preview)) return undefined const storyboard = videoObject.preview.find(p => p.rel.includes('storyboard')) @@ -192,7 +190,7 @@ function getStoryboardAttributeFromObject (video: MVideoId, videoObject: VideoOb } } -function getVideoAttributesFromObject (videoChannel: MChannelId, videoObject: VideoObject, to: string[] = []) { +export function getVideoAttributesFromObject (videoChannel: MChannelId, videoObject: VideoObject, to: string[] = []) { const privacy = to.includes(ACTIVITY_PUB.PUBLIC) ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED @@ -247,23 +245,7 @@ function getVideoAttributesFromObject (videoChannel: MChannelId, videoObject: Vi } // --------------------------------------------------------------------------- - -export { - getThumbnailFromIcons, - getPreviewFromIcons, - - getTagsFromObject, - - getFileAttributesFromUrl, - getStreamingPlaylistAttributesFromObject, - - getLiveAttributesFromObject, - getCaptionAttributesFromObject, - getStoryboardAttributeFromObject, - - getVideoAttributesFromObject -} - +// Private // --------------------------------------------------------------------------- function isAPVideoUrlObject (url: any): url is ActivityVideoUrlObject { diff --git a/server/core/lib/actor-image.ts b/server/core/lib/actor-image.ts deleted file mode 100644 index 5b19a740d..000000000 --- a/server/core/lib/actor-image.ts +++ /dev/null @@ -1,14 +0,0 @@ -import maxBy from 'lodash-es/maxBy.js' - -function getBiggestActorImage (images: T[]) { - const image = maxBy(images, 'width') - - // If width is null, maxBy won't return a value - if (!image) return images[0] - - return image -} - -export { - getBiggestActorImage -} diff --git a/server/core/lib/html/shared/actor-html.ts b/server/core/lib/html/shared/actor-html.ts index 0a9056483..2c6990afa 100644 --- a/server/core/lib/html/shared/actor-html.ts +++ b/server/core/lib/html/shared/actor-html.ts @@ -1,14 +1,13 @@ -import { escapeHTML } from '@peertube/peertube-core-utils' +import { escapeHTML, maxBy } from '@peertube/peertube-core-utils' import { HttpStatusCode } from '@peertube/peertube-models' -import express from 'express' -import { CONFIG } from '../../../initializers/config.js' import { AccountModel } from '@server/models/account/account.js' +import { ActorImageModel } from '@server/models/actor/actor-image.js' import { VideoChannelModel } from '@server/models/video/video-channel.js' import { MAccountHost, MChannelHost } from '@server/types/models/index.js' -import { getBiggestActorImage } from '@server/lib/actor-image.js' -import { ActorImageModel } from '@server/models/actor/actor-image.js' -import { TagsHtml } from './tags-html.js' +import express from 'express' +import { CONFIG } from '../../../initializers/config.js' import { PageHtml } from './page-html.js' +import { TagsHtml } from './tags-html.js' export class ActorHtml { @@ -60,7 +59,7 @@ export class ActorHtml { const siteName = CONFIG.INSTANCE.NAME const title = entity.getDisplayName() - const avatar = getBiggestActorImage(entity.Actor.Avatars) + const avatar = maxBy(entity.Actor.Avatars, 'width') const image = { url: ActorImageModel.getImageUrl(avatar), width: avatar?.width, diff --git a/server/core/lib/user-import-export/exporters/actor-exporter.ts b/server/core/lib/user-import-export/exporters/actor-exporter.ts index 8b48d08e7..12e54ca6d 100644 --- a/server/core/lib/user-import-export/exporters/actor-exporter.ts +++ b/server/core/lib/user-import-export/exporters/actor-exporter.ts @@ -13,7 +13,10 @@ export abstract class ActorExporter extends AbstractUserExporter { name: actor.preferredUsername, - avatars: this.exportActorImageJSON(actor.Avatars), + avatars: actor.hasImage(ActorImageType.AVATAR) + ? this.exportActorImageJSON(actor.Avatars) + : [], + banners: actor.hasImage(ActorImageType.BANNER) ? this.exportActorImageJSON(actor.Banners) : [] diff --git a/server/core/models/actor/actor.ts b/server/core/models/actor/actor.ts index 6ca820b82..1ec75bb22 100644 --- a/server/core/models/actor/actor.ts +++ b/server/core/models/actor/actor.ts @@ -1,12 +1,10 @@ -import { forceNumber } from '@peertube/peertube-core-utils' +import { forceNumber, maxBy } from '@peertube/peertube-core-utils' import { ActivityIconObject, ActorImageType, ActorImageType_Type, type ActivityPubActorType } from '@peertube/peertube-models' -import { getLowercaseExtension } from '@peertube/peertube-node-utils' import { AttributesOnly } from '@peertube/peertube-typescript-utils' import { activityPubContextify } from '@server/helpers/activity-pub-utils.js' import { getContextFilter } from '@server/lib/activitypub/context.js' -import { getBiggestActorImage } from '@server/lib/actor-image.js' import { ModelCache } from '@server/models/shared/model-cache.js' -import { col, fn, literal, Op, QueryTypes, Transaction, where } from 'sequelize' +import { Op, QueryTypes, Transaction, col, fn, literal, where } from 'sequelize' import { AllowNull, BelongsTo, @@ -33,16 +31,14 @@ import { isActivityPubUrlValid } from '../../helpers/custom-validators/activityp import { ACTIVITY_PUB, ACTIVITY_PUB_ACTOR_TYPES, - CONSTRAINTS_FIELDS, - MIMETYPES, - SERVER_ACTOR_NAME, + CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants.js' import { MActor, - MActorAccountChannelId, MActorAPAccount, MActorAPChannel, + MActorAccountChannelId, MActorFollowersUrl, MActorFormattable, MActorFull, @@ -61,7 +57,6 @@ import { VideoChannelModel } from '../video/video-channel.js' import { VideoModel } from '../video/video.js' import { ActorFollowModel } from './actor-follow.js' import { ActorImageModel } from './actor-image.js' -import maxBy from 'lodash-es/maxBy.js' enum ScopeNames { FULL = 'FULL' @@ -562,24 +557,15 @@ export class ActorModel extends SequelizeModel { } toActivityPubObject (this: MActorAPChannel | MActorAPAccount, name: string) { - let icon: ActivityIconObject[] - let image: ActivityIconObject + let icon: ActivityIconObject[] // Avatars + let image: ActivityIconObject[] // Banners if (this.hasImage(ActorImageType.AVATAR)) { icon = this.Avatars.map(a => a.toActivityPubObject()) } if (this.hasImage(ActorImageType.BANNER)) { - const banner = getBiggestActorImage((this as MActorAPChannel).Banners) - const extension = getLowercaseExtension(banner.filename) - - image = { - type: 'Image', - mediaType: MIMETYPES.IMAGE.EXT_MIMETYPE[extension], - height: banner.height, - width: banner.width, - url: ActorImageModel.getImageUrl(banner) - } + image = (this as MActorAPChannel).Banners.map(b => b.toActivityPubObject()) } const json = { diff --git a/server/core/models/user/user-notification.ts b/server/core/models/user/user-notification.ts index 3f0b77711..2765dbf2a 100644 --- a/server/core/models/user/user-notification.ts +++ b/server/core/models/user/user-notification.ts @@ -1,7 +1,6 @@ -import { forceNumber } from '@peertube/peertube-core-utils' +import { forceNumber, maxBy } from '@peertube/peertube-core-utils' import { UserNotification, type UserNotificationType_Type } from '@peertube/peertube-models' import { uuidToShort } from '@peertube/peertube-node-utils' -import { getBiggestActorImage } from '@server/lib/actor-image.js' import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user/index.js' import { ModelIndexesOptions, Op, WhereOptions } from 'sequelize' import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Table, UpdatedAt } from 'sequelize-typescript' @@ -518,7 +517,7 @@ export class UserNotificationModel extends SequelizeModel if (!avatars || avatars.length === 0) return { avatar: undefined, avatars: [] } return { - avatar: this.formatAvatar(getBiggestActorImage(avatars)), + avatar: this.formatAvatar(maxBy(avatars, 'width')), avatars: avatars.map(a => this.formatAvatar(a)) } diff --git a/server/core/models/video/video.ts b/server/core/models/video/video.ts index d01a07c04..22643bbbb 100644 --- a/server/core/models/video/video.ts +++ b/server/core/models/video/video.ts @@ -1,4 +1,4 @@ -import { buildVideoEmbedPath, buildVideoWatchPath, pick, wait } from '@peertube/peertube-core-utils' +import { buildVideoEmbedPath, buildVideoWatchPath, maxBy, minBy, pick, wait } from '@peertube/peertube-core-utils' import { ffprobePromise, getAudioStream, getVideoStreamDimensionsInfo, getVideoStreamFPS, hasAudioStream } from '@peertube/peertube-ffmpeg' import { FileStorage, @@ -38,8 +38,6 @@ import { ModelCache } from '@server/models/shared/model-cache.js' import { MVideoSource } from '@server/types/models/video/video-source.js' import Bluebird from 'bluebird' import { remove } from 'fs-extra/esm' -import maxBy from 'lodash-es/maxBy.js' -import minBy from 'lodash-es/minBy.js' import { FindOptions, IncludeOptions, Includeable, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' import { AfterCreate, @@ -1711,9 +1709,9 @@ export class VideoModel extends SequelizeModel { return this.VideoChannel.Account.Actor.Server?.isBlocked() || this.VideoChannel.Account.isBlocked() } - getQualityFileBy (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) { + getQualityFileBy (this: T, fun: (files: MVideoFile[], property: 'resolution') => MVideoFile) { const files = this.getAllFiles() - const file = fun(files, file => file.resolution) + const file = fun(files, 'resolution') if (!file) return undefined if (file.videoId) { diff --git a/server/scripts/migrations/peertube-4.2.ts b/server/scripts/migrations/peertube-4.2.ts index 6c89ee39e..9fddaa0f4 100644 --- a/server/scripts/migrations/peertube-4.2.ts +++ b/server/scripts/migrations/peertube-4.2.ts @@ -1,3 +1,4 @@ +import { maxBy, minBy } from '@peertube/peertube-core-utils' import { ActorImageType } from '@peertube/peertube-models' import { buildUUID, getLowercaseExtension } from '@peertube/peertube-node-utils' import { getImageSize, processImage } from '@server/helpers/image-utils.js' @@ -6,13 +7,11 @@ import { ACTOR_IMAGES_SIZE } from '@server/initializers/constants.js' import { initDatabaseModels } from '@server/initializers/database.js' import { updateActorImages } from '@server/lib/activitypub/actors/index.js' import { sendUpdateActor } from '@server/lib/activitypub/send/index.js' -import { getBiggestActorImage } from '@server/lib/actor-image.js' import { JobQueue } from '@server/lib/job-queue/index.js' import { AccountModel } from '@server/models/account/account.js' import { ActorModel } from '@server/models/actor/actor.js' import { VideoChannelModel } from '@server/models/video/video-channel.js' import { MAccountDefault, MActorDefault, MChannelDefault } from '@server/types/models/index.js' -import minBy from 'lodash-es/minBy.js' import { join } from 'path' run() @@ -100,7 +99,7 @@ async function generateSmallerAvatarIfNeeded (accountOrChannel: MAccountDefault } async function generateSmallerAvatar (actor: MActorDefault) { - const bigAvatar = getBiggestActorImage(actor.Avatars) + const bigAvatar = maxBy(actor.Avatars, 'width') const imageSize = minBy(ACTOR_IMAGES_SIZE[ActorImageType.AVATAR], 'width') const sourceFilename = bigAvatar.filename