mirror of https://github.com/Chocobozzz/PeerTube
Generate small versions of banners too
parent
aaa5acbb0c
commit
11521f231f
|
@ -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 { AfterViewChecked, Component, ElementRef, OnInit, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute, RouterLink } from '@angular/router'
|
import { ActivatedRoute, RouterLink } from '@angular/router'
|
||||||
import { Notifier, ServerService } from '@app/core'
|
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 { HTMLServerConfig, ServerStats } from '@peertube/peertube-models'
|
||||||
import { copyToClipboard } from '@root-helpers/utils'
|
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 { ResolverData } from './about-instance.resolver'
|
||||||
import { ContactAdminModalComponent } from './contact-admin-modal.component'
|
import { ContactAdminModalComponent } from './contact-admin-modal.component'
|
||||||
import { InstanceStatisticsComponent } from './instance-statistics.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({
|
@Component({
|
||||||
selector: 'my-about-instance',
|
selector: 'my-about-instance',
|
||||||
|
@ -82,7 +83,7 @@ export class AboutInstanceComponent implements OnInit, AfterViewChecked {
|
||||||
this.shortDescription = about.instance.shortDescription
|
this.shortDescription = about.instance.shortDescription
|
||||||
|
|
||||||
this.instanceBannerUrl = about.instance.banners.length !== 0
|
this.instanceBannerUrl = about.instance.banners.length !== 0
|
||||||
? about.instance.banners[0].path
|
? maxBy(about.instance.banners, 'width').path
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
this.serverConfig = this.serverService.getHTMLConfig()
|
this.serverConfig = this.serverService.getHTMLConfig()
|
||||||
|
|
|
@ -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 { Component, Input, OnInit } from '@angular/core'
|
||||||
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'
|
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 { RouterLink } from '@angular/router'
|
||||||
import { SelectCheckboxComponent } from '../../../shared/shared-forms/select/select-checkbox.component'
|
import { Notifier, ServerService } from '@app/core'
|
||||||
import { MarkdownTextareaComponent } from '../../../shared/shared-forms/markdown-textarea.component'
|
import { genericUploadErrorHandler } from '@app/helpers'
|
||||||
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 { CustomMarkupService } from '@app/shared/shared-custom-markup/custom-markup.service'
|
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({
|
@Component({
|
||||||
selector: 'my-edit-instance-information',
|
selector: 'my-edit-instance-information',
|
||||||
|
@ -127,7 +128,7 @@ export class EditInstanceInformationComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateActorImages () {
|
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
|
this.instanceAvatars = this.serverConfig.instance.avatars
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
import { ChartData, ChartOptions, TooltipItem, TooltipModel } from 'chart.js'
|
import { NgFor, NgIf } from '@angular/common'
|
||||||
import { max, maxBy, min, minBy } from 'lodash-es'
|
|
||||||
import { Subject, first, map, switchMap } from 'rxjs'
|
|
||||||
import { Component } from '@angular/core'
|
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 { RouterLink } from '@angular/router'
|
||||||
import { NgIf, NgFor } from '@angular/common'
|
import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, hasMoreItems } from '@app/core'
|
||||||
import { GlobalIconComponent } from '../../shared/shared-icons/global-icon.component'
|
import { formatICU } from '@app/helpers'
|
||||||
import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model'
|
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 { 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({
|
@Component({
|
||||||
templateUrl: './my-video-channels.component.html',
|
templateUrl: './my-video-channels.component.html',
|
||||||
|
@ -156,23 +156,8 @@ export class MyVideoChannelsComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildChartOptions () {
|
private buildChartOptions () {
|
||||||
// chart options that depend on chart data:
|
const channelsMinimumDailyViews = Math.min(...this.videoChannels.map(v => minBy(v.viewsPerDay, 'views').views))
|
||||||
// we don't want to skew values and have min at 0, so we define what the floor/ceiling is here
|
const channelsMaximumDailyViews = Math.max(...this.videoChannels.map(v => maxBy(v.viewsPerDay, 'views').views))
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
this.chartOptions = {
|
this.chartOptions = {
|
||||||
plugins: {
|
plugins: {
|
||||||
|
@ -199,8 +184,8 @@ export class MyVideoChannelsComponent {
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
display: false,
|
display: false,
|
||||||
min: Math.max(0, videoChannelsMinimumDailyViews - (3 * videoChannelsMaximumDailyViews / 100)),
|
min: Math.max(0, channelsMinimumDailyViews - (3 * channelsMaximumDailyViews / 100)),
|
||||||
max: Math.max(1, videoChannelsMaximumDailyViews)
|
max: Math.max(1, channelsMaximumDailyViews)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
|
|
|
@ -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 { first, map } from 'rxjs/operators'
|
||||||
import { SelectChannelItem } from 'src/types/select-options-item.model'
|
import { SelectChannelItem } from 'src/types/select-options-item.model'
|
||||||
import { VideoChannel } from '@peertube/peertube-models'
|
|
||||||
import { AuthService } from '../../core/auth'
|
import { AuthService } from '../../core/auth'
|
||||||
|
|
||||||
function listUserChannelsForSelect (authService: AuthService) {
|
function listUserChannelsForSelect (authService: AuthService) {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import { NgClass, NgIf } from '@angular/common'
|
||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'
|
||||||
import { CustomMarkupComponent } from './shared'
|
|
||||||
import { ServerService } from '@app/core'
|
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
|
* Markup component that creates the img HTML element containing the instance banner
|
||||||
|
@ -28,7 +29,7 @@ export class InstanceBannerMarkupComponent implements OnInit, CustomMarkupCompon
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
const { instance } = this.server.getHTMLConfig()
|
const { instance } = this.server.getHTMLConfig()
|
||||||
|
|
||||||
this.instanceBannerUrl = instance.banners?.[0]?.path
|
this.instanceBannerUrl = maxBy(instance.banners, 'width')?.path
|
||||||
this.cd.markForCheck()
|
this.cd.markForCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { NgClass, NgIf } from '@angular/common'
|
||||||
import { Component, Input, OnInit, booleanAttribute } from '@angular/core'
|
import { Component, Input, OnInit, booleanAttribute } from '@angular/core'
|
||||||
import { ServerService } from '@app/core'
|
import { ServerService } from '@app/core'
|
||||||
import { NgIf, NgClass } from '@angular/common'
|
import { maxBy } from '@peertube/peertube-core-utils'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-instance-banner',
|
selector: 'my-instance-banner',
|
||||||
|
@ -20,6 +21,6 @@ export class InstanceBannerComponent implements OnInit {
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
const { instance } = this.server.getHTMLConfig()
|
const { instance } = this.server.getHTMLConfig()
|
||||||
|
|
||||||
this.instanceBannerUrl = instance.banners?.[0]?.path
|
this.instanceBannerUrl = maxBy(instance.banners, 'width')?.path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { getAbsoluteAPIUrl } from '@app/helpers'
|
import { getAbsoluteAPIUrl } from '@app/helpers'
|
||||||
import { Account as ServerAccount, ActorImage, VideoChannel as ServerVideoChannel, ViewsPerDate } from '@peertube/peertube-models'
|
import { Account as ServerAccount, ActorImage, VideoChannel as ServerVideoChannel, ViewsPerDate } from '@peertube/peertube-models'
|
||||||
import { Actor } from '../account/actor.model'
|
import { Actor } from '../account/actor.model'
|
||||||
|
import { maxBy } from '@peertube/peertube-core-utils'
|
||||||
|
|
||||||
export class VideoChannel extends Actor implements ServerVideoChannel {
|
export class VideoChannel extends Actor implements ServerVideoChannel {
|
||||||
displayName: string
|
displayName: string
|
||||||
|
@ -35,7 +36,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const banner = channel.banners[0]
|
const banner = maxBy(channel.banners, 'width')
|
||||||
if (!banner) return ''
|
if (!banner) return ''
|
||||||
|
|
||||||
if (banner.url) return banner.url
|
if (banner.url) return banner.url
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import { VideoFile } from '@peertube/peertube-models'
|
export function toTitleCase (str: string) {
|
||||||
|
|
||||||
function toTitleCase (str: string) {
|
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,36 +8,14 @@ const dictionaryBytes = [
|
||||||
{ max: 1073741824, type: 'MB', decimals: 0 },
|
{ max: 1073741824, type: 'MB', decimals: 0 },
|
||||||
{ max: 1.0995116e12, type: 'GB', decimals: 1 }
|
{ 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 format = dictionaryBytes.find(d => value < d.max) || dictionaryBytes[dictionaryBytes.length - 1]
|
||||||
const calc = (value / (format.max / 1024)).toFixed(format.decimals)
|
const calc = (value / (format.max / 1024)).toFixed(format.decimals)
|
||||||
|
|
||||||
return [ calc, format.type ]
|
return [ calc, format.type ]
|
||||||
}
|
}
|
||||||
|
|
||||||
function videoFileMaxByResolution (files: VideoFile[]) {
|
export function getRtcConfig () {
|
||||||
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 {
|
return {
|
||||||
iceServers: [
|
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
|
return new URL(current).origin === new URL(target).origin
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
getRtcConfig,
|
|
||||||
toTitleCase,
|
|
||||||
|
|
||||||
videoFileMaxByResolution,
|
|
||||||
videoFileMinByResolution,
|
|
||||||
bytes,
|
|
||||||
|
|
||||||
isSameOrigin
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -43,3 +43,23 @@ export function sortBy (obj: any[], key1: string, key2?: string) {
|
||||||
return 1
|
return 1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function maxBy <T> (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 <T> (arr: T[], property: keyof T) {
|
||||||
|
let result: T
|
||||||
|
|
||||||
|
for (const obj of arr) {
|
||||||
|
if (!result || result[property] > obj[property]) result = obj
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -1,19 +1,19 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
import { expect } from 'chai'
|
|
||||||
import { parallelTests } from '@peertube/peertube-node-utils'
|
|
||||||
import { ActorImageType, CustomConfig, HttpStatusCode } from '@peertube/peertube-models'
|
import { ActorImageType, CustomConfig, HttpStatusCode } from '@peertube/peertube-models'
|
||||||
|
import { parallelTests } from '@peertube/peertube-node-utils'
|
||||||
import {
|
import {
|
||||||
|
PeerTubeServer,
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
createSingleServer,
|
createSingleServer,
|
||||||
killallServers,
|
killallServers,
|
||||||
makeActivityPubGetRequest,
|
makeActivityPubGetRequest,
|
||||||
makeGetRequest,
|
makeGetRequest,
|
||||||
makeRawRequest,
|
makeRawRequest,
|
||||||
PeerTubeServer,
|
|
||||||
setAccessTokensToServers
|
setAccessTokensToServers
|
||||||
} from '@peertube/peertube-server-commands'
|
} from '@peertube/peertube-server-commands'
|
||||||
import { testFileExistsOrNot, testImage, testAvatarSize } from '@tests/shared/checks.js'
|
import { testAvatarSize, testFileExistsOnFSOrNot, testImage } from '@tests/shared/checks.js'
|
||||||
|
import { expect } from 'chai'
|
||||||
import { basename } from 'path'
|
import { basename } from 'path'
|
||||||
|
|
||||||
function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
|
function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
|
||||||
|
@ -703,18 +703,21 @@ describe('Test config', function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Banner', function () {
|
describe('Banner', function () {
|
||||||
let bannerPath: string
|
const bannerPaths: string[] = []
|
||||||
|
|
||||||
it('Should update instance banner', async function () {
|
it('Should update instance banner', async function () {
|
||||||
await server.config.updateInstanceImage({ type: ActorImageType.BANNER, fixture: 'banner.jpg' })
|
await server.config.updateInstanceImage({ type: ActorImageType.BANNER, fixture: 'banner.jpg' })
|
||||||
|
|
||||||
const { banners } = await checkAndGetServerImages()
|
const { banners } = await checkAndGetServerImages()
|
||||||
|
|
||||||
expect(banners).to.have.lengthOf(1)
|
expect(banners).to.have.lengthOf(2)
|
||||||
|
|
||||||
bannerPath = banners[0].path
|
for (const banner of banners) {
|
||||||
await testImage(server.url, 'banner-resized', bannerPath)
|
await testImage(server.url, `banner-resized-${banner.width}`, banner.path)
|
||||||
await testFileExistsOrNot(server, 'avatars', basename(bannerPath), true)
|
await testFileExistsOnFSOrNot(server, 'avatars', basename(banner.path), true)
|
||||||
|
|
||||||
|
bannerPaths.push(banner.path)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should re-update an existing instance banner', async function () {
|
it('Should re-update an existing instance banner', async function () {
|
||||||
|
@ -727,12 +730,14 @@ describe('Test config', function () {
|
||||||
const { banners } = await checkAndGetServerImages()
|
const { banners } = await checkAndGetServerImages()
|
||||||
expect(banners).to.have.lengthOf(0)
|
expect(banners).to.have.lengthOf(0)
|
||||||
|
|
||||||
await testFileExistsOrNot(server, 'avatars', basename(bannerPath), false)
|
for (const bannerPath of bannerPaths) {
|
||||||
|
await testFileExistsOnFSOrNot(server, 'avatars', basename(bannerPath), false)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Avatar', function () {
|
describe('Avatar', function () {
|
||||||
let avatarPath: string
|
const avatarPaths: string[] = []
|
||||||
|
|
||||||
it('Should update instance avatar', async function () {
|
it('Should update instance avatar', async function () {
|
||||||
for (const extension of [ '.png', '.gif' ]) {
|
for (const extension of [ '.png', '.gif' ]) {
|
||||||
|
@ -744,10 +749,10 @@ describe('Test config', function () {
|
||||||
|
|
||||||
for (const avatar of avatars) {
|
for (const avatar of avatars) {
|
||||||
await testAvatarSize({ url: server.url, avatar, imageName: `avatar-resized-${avatar.width}x${avatar.width}` })
|
await testAvatarSize({ url: server.url, avatar, imageName: `avatar-resized-${avatar.width}x${avatar.width}` })
|
||||||
}
|
await testFileExistsOnFSOrNot(server, 'avatars', basename(avatar.path), true)
|
||||||
|
|
||||||
avatarPath = avatars[0].path
|
avatarPaths.push(avatar.path)
|
||||||
await testFileExistsOrNot(server, 'avatars', basename(avatarPath), true)
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -768,7 +773,9 @@ describe('Test config', function () {
|
||||||
const { avatars } = await checkAndGetServerImages()
|
const { avatars } = await checkAndGetServerImages()
|
||||||
expect(avatars).to.have.lengthOf(0)
|
expect(avatars).to.have.lengthOf(0)
|
||||||
|
|
||||||
await testFileExistsOrNot(server, 'avatars', basename(avatarPath), false)
|
for (const avatarPath of avatarPaths) {
|
||||||
|
await testFileExistsOnFSOrNot(server, 'avatars', basename(avatarPath), false)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should not have the avatars anymore in the AP representation of the instance', async function () {
|
it('Should not have the avatars anymore in the AP representation of the instance', async function () {
|
||||||
|
|
|
@ -444,7 +444,7 @@ function runTest (withObjectStorage: boolean) {
|
||||||
expect(secondaryChannel.support).to.equal('noah support')
|
expect(secondaryChannel.support).to.equal('noah support')
|
||||||
|
|
||||||
expect(secondaryChannel.avatars).to.have.lengthOf(4)
|
expect(secondaryChannel.avatars).to.have.lengthOf(4)
|
||||||
expect(secondaryChannel.banners).to.have.lengthOf(1)
|
expect(secondaryChannel.banners).to.have.lengthOf(2)
|
||||||
|
|
||||||
const urls = [ ...secondaryChannel.avatars, ...secondaryChannel.banners ].map(a => a.url)
|
const urls = [ ...secondaryChannel.avatars, ...secondaryChannel.banners ].map(a => a.url)
|
||||||
for (const url of urls) {
|
for (const url of urls) {
|
||||||
|
|
|
@ -198,7 +198,9 @@ function runTest (withObjectStorage: boolean) {
|
||||||
expect(importedSecond.description).to.equal('noah description')
|
expect(importedSecond.description).to.equal('noah description')
|
||||||
expect(importedSecond.support).to.equal('noah support')
|
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) {
|
for (const avatar of importedSecond.avatars) {
|
||||||
await testImage(remoteServer.url, `avatar-resized-${avatar.width}x${avatar.width}`, avatar.path, '.png')
|
await testImage(remoteServer.url, `avatar-resized-${avatar.width}x${avatar.width}`, avatar.path, '.png')
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
/* 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 { wait } from '@peertube/peertube-core-utils'
|
||||||
import { ActorImageType, User, VideoChannel } from '@peertube/peertube-models'
|
import { ActorImageType, User, VideoChannel } from '@peertube/peertube-models'
|
||||||
import {
|
import {
|
||||||
|
PeerTubeServer,
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
createMultipleServers,
|
createMultipleServers,
|
||||||
doubleFollow,
|
doubleFollow,
|
||||||
PeerTubeServer,
|
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
setDefaultAccountAvatar,
|
setDefaultAccountAvatar,
|
||||||
setDefaultVideoChannel,
|
setDefaultVideoChannel,
|
||||||
waitJobs
|
waitJobs
|
||||||
} from '@peertube/peertube-server-commands'
|
} 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) {
|
async function findChannel (server: PeerTubeServer, channelId: number) {
|
||||||
const body = await server.channels.list({ sort: '-name' })
|
const body = await server.channels.list({ sort: '-name' })
|
||||||
|
@ -294,7 +294,7 @@ describe('Test video channels', function () {
|
||||||
for (const avatar of videoChannel.avatars) {
|
for (const avatar of videoChannel.avatars) {
|
||||||
avatarPaths[server.port] = avatar.path
|
avatarPaths[server.port] = avatar.path
|
||||||
await testImage(server.url, `avatar-resized-${avatar.width}x${avatar.width}`, avatarPaths[server.port], '.png')
|
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]))
|
const row = await sqlCommands[i].getActorImage(basename(avatarPaths[server.port]))
|
||||||
|
|
||||||
|
@ -320,14 +320,18 @@ describe('Test video channels', function () {
|
||||||
const server = servers[i]
|
const server = servers[i]
|
||||||
|
|
||||||
const videoChannel = await server.channels.get({ channelName: 'second_video_channel@' + servers[0].host })
|
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
|
expect(videoChannel.banners.length).to.equal(expectedSizes.length, 'Expected banners to be generated in all sizes')
|
||||||
await testImage(server.url, 'banner-resized', bannerPaths[server.port])
|
|
||||||
await testFileExistsOrNot(server, 'avatars', basename(bannerPaths[server.port]), true)
|
|
||||||
|
|
||||||
const row = await sqlCommands[i].getActorImage(basename(bannerPaths[server.port]))
|
for (const banner of videoChannel.banners) {
|
||||||
expect(row.height).to.equal(ACTOR_IMAGES_SIZE[ActorImageType.BANNER][0].height)
|
bannerPaths[server.port] = banner.path
|
||||||
expect(row.width).to.equal(ACTOR_IMAGES_SIZE[ActorImageType.BANNER][0].width)
|
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) {
|
for (const server of servers) {
|
||||||
const videoChannel = await findChannel(server, secondVideoChannelId)
|
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
|
expect(videoChannel.avatars).to.be.empty
|
||||||
}
|
}
|
||||||
|
@ -372,7 +376,7 @@ describe('Test video channels', function () {
|
||||||
|
|
||||||
for (const server of servers) {
|
for (const server of servers) {
|
||||||
const videoChannel = await findChannel(server, secondVideoChannelId)
|
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
|
expect(videoChannel.banners).to.be.empty
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
const base = server.servers.buildDirectory(directory)
|
||||||
|
|
||||||
expect(await pathExists(join(base, filePath))).to.equal(exist)
|
expect(await pathExists(join(base, filePath))).to.equal(exist)
|
||||||
|
@ -182,7 +182,7 @@ export {
|
||||||
testAvatarSize,
|
testAvatarSize,
|
||||||
testImage,
|
testImage,
|
||||||
expectLogDoesNotContain,
|
expectLogDoesNotContain,
|
||||||
testFileExistsOrNot,
|
testFileExistsOnFSOrNot,
|
||||||
expectStartWith,
|
expectStartWith,
|
||||||
expectNotStartWith,
|
expectNotStartWith,
|
||||||
expectEndWith,
|
expectEndWith,
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { resolve } from 'path'
|
||||||
import { MockSmtpServer } from './mock-servers/mock-email.js'
|
import { MockSmtpServer } from './mock-servers/mock-email.js'
|
||||||
import { getAllNotificationsSettings } from './notifications.js'
|
import { getAllNotificationsSettings } from './notifications.js'
|
||||||
import { getFilenameFromUrl } from '@peertube/peertube-node-utils'
|
import { getFilenameFromUrl } from '@peertube/peertube-node-utils'
|
||||||
import { testFileExistsOrNot } from './checks.js'
|
import { testFileExistsOnFSOrNot } from './checks.js'
|
||||||
|
|
||||||
type ExportOutbox = ActivityPubOrderedCollection<ActivityCreate<VideoObject | VideoCommentObject>>
|
type ExportOutbox = ActivityPubOrderedCollection<ActivityCreate<VideoObject | VideoCommentObject>>
|
||||||
|
|
||||||
|
@ -101,10 +101,10 @@ export async function checkExportFileExists (options: {
|
||||||
return makeRawRequest({ url: redirectedUrl, expectedStatus: HttpStatusCode.OK_200 })
|
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) {
|
if (withObjectStorage) {
|
||||||
await makeRawRequest({ url: redirectedUrl, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
await makeRawRequest({ url: redirectedUrl, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import express from 'express'
|
|
||||||
import { Feed } from '@peertube/feed'
|
import { Feed } from '@peertube/feed'
|
||||||
import { CustomTag, CustomXMLNS, Person } from '@peertube/feed/lib/typings/index.js'
|
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 { mdToOneLinePlainText } from '@server/helpers/markdown.js'
|
||||||
import { CONFIG } from '@server/initializers/config.js'
|
import { CONFIG } from '@server/initializers/config.js'
|
||||||
import { WEBSERVER } from '@server/initializers/constants.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 { UserModel } from '@server/models/user/user.js'
|
||||||
import { MAccountDefault, MChannelBannerAccountDefault, MUser, MVideoFullLight } from '@server/types/models/index.js'
|
import { MAccountDefault, MChannelBannerAccountDefault, MUser, MVideoFullLight } from '@server/types/models/index.js'
|
||||||
import { pick } from '@peertube/peertube-core-utils'
|
import express from 'express'
|
||||||
import { ActorImageType } from '@peertube/peertube-models'
|
|
||||||
|
|
||||||
export function initFeed (parameters: {
|
export function initFeed (parameters: {
|
||||||
name: string
|
name: string
|
||||||
|
@ -105,12 +104,12 @@ export async function buildFeedMetadata (options: {
|
||||||
accountLink = videoChannel.Account.getClientUrl()
|
accountLink = videoChannel.Account.getClientUrl()
|
||||||
|
|
||||||
if (videoChannel.Actor.hasImage(ActorImageType.AVATAR)) {
|
if (videoChannel.Actor.hasImage(ActorImageType.AVATAR)) {
|
||||||
const videoChannelAvatar = getBiggestActorImage(videoChannel.Actor.Avatars)
|
const videoChannelAvatar = maxBy(videoChannel.Actor.Avatars, 'width')
|
||||||
imageUrl = WEBSERVER.URL + videoChannelAvatar.getStaticPath()
|
imageUrl = WEBSERVER.URL + videoChannelAvatar.getStaticPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoChannel.Account.Actor.hasImage(ActorImageType.AVATAR)) {
|
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()
|
accountImageUrl = WEBSERVER.URL + accountAvatar.getStaticPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +122,7 @@ export async function buildFeedMetadata (options: {
|
||||||
accountLink = link
|
accountLink = link
|
||||||
|
|
||||||
if (account.Actor.hasImage(ActorImageType.AVATAR)) {
|
if (account.Actor.hasImage(ActorImageType.AVATAR)) {
|
||||||
const accountAvatar = getBiggestActorImage(account.Actor.Avatars)
|
const accountAvatar = maxBy(account.Actor.Avatars, 'width')
|
||||||
imageUrl = WEBSERVER.URL + accountAvatar?.getStaticPath()
|
imageUrl = WEBSERVER.URL + accountAvatar?.getStaticPath()
|
||||||
accountImageUrl = imageUrl
|
accountImageUrl = imageUrl
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
import express from 'express'
|
|
||||||
import { extname } from 'path'
|
|
||||||
import { Feed } from '@peertube/feed'
|
import { Feed } from '@peertube/feed'
|
||||||
import { CustomTag, CustomXMLNS, LiveItemStatus } from '@peertube/feed/lib/typings/index.js'
|
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 { InternalEventEmitter } from '@server/lib/internal-event-emitter.js'
|
||||||
import { Hooks } from '@server/lib/plugins/hooks.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 { buildPodcastGroupsCache, cacheRouteFactory, videoFeedsPodcastSetCacheKey } from '@server/middlewares/index.js'
|
||||||
import { MVideo, MVideoCaptionVideo, MVideoFullLight } from '@server/types/models/index.js'
|
import { MVideo, MVideoCaptionVideo, MVideoFullLight } from '@server/types/models/index.js'
|
||||||
import { sortObjectComparator } from '@peertube/peertube-core-utils'
|
import express from 'express'
|
||||||
import { ActorImageType, VideoFile, VideoInclude, VideoResolution, VideoState } from '@peertube/peertube-models'
|
import { extname } from 'path'
|
||||||
import { buildNSFWFilter } from '../../helpers/express-utils.js'
|
import { buildNSFWFilter } from '../../helpers/express-utils.js'
|
||||||
import { MIMETYPES, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants.js'
|
import { MIMETYPES, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants.js'
|
||||||
import { asyncMiddleware, setFeedPodcastContentType, videoFeedsPodcastValidator } from '../../middlewares/index.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 { VideoCaptionModel } from '../../models/video/video-caption.js'
|
||||||
|
import { VideoModel } from '../../models/video/video.js'
|
||||||
import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed } from './shared/index.js'
|
import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed } from './shared/index.js'
|
||||||
import { getVideoFileMimeType } from '@server/lib/video-file.js'
|
|
||||||
|
|
||||||
const videoPodcastFeedsRouter = express.Router()
|
const videoPodcastFeedsRouter = express.Router()
|
||||||
|
|
||||||
|
@ -151,7 +150,7 @@ async function generatePodcastItem (options: {
|
||||||
let personImage: string
|
let personImage: string
|
||||||
|
|
||||||
if (account.Actor.hasImage(ActorImageType.AVATAR)) {
|
if (account.Actor.hasImage(ActorImageType.AVATAR)) {
|
||||||
const avatar = getBiggestActorImage(account.Actor.Avatars)
|
const avatar = maxBy(account.Actor.Avatars, 'width')
|
||||||
personImage = WEBSERVER.URL + avatar.getStaticPath()
|
personImage = WEBSERVER.URL + avatar.getStaticPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -898,7 +898,7 @@ const PREVIEWS_SIZE = {
|
||||||
minWidth: 400
|
minWidth: 400
|
||||||
}
|
}
|
||||||
const ACTOR_IMAGES_SIZE: { [key in ActorImageType_Type]: { width: number, height: number }[] } = {
|
const ACTOR_IMAGES_SIZE: { [key in ActorImageType_Type]: { width: number, height: number }[] } = {
|
||||||
[ActorImageType.AVATAR]: [
|
[ActorImageType.AVATAR]: [ // 1/1 ratio
|
||||||
{
|
{
|
||||||
width: 1500,
|
width: 1500,
|
||||||
height: 1500
|
height: 1500
|
||||||
|
@ -916,10 +916,14 @@ const ACTOR_IMAGES_SIZE: { [key in ActorImageType_Type]: { width: number, height
|
||||||
height: 48
|
height: 48
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[ActorImageType.BANNER]: [
|
[ActorImageType.BANNER]: [ // 6/1 ratio
|
||||||
{
|
{
|
||||||
width: 1920,
|
width: 1920,
|
||||||
height: 317 // 6/1 ratio
|
height: 317
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 600,
|
||||||
|
height: 100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { arrayify } from '@peertube/peertube-core-utils'
|
import { arrayify, maxBy, minBy } from '@peertube/peertube-core-utils'
|
||||||
import {
|
import {
|
||||||
ActivityHashTagObject,
|
ActivityHashTagObject,
|
||||||
ActivityMagnetUrlObject,
|
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 { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist.js'
|
||||||
import { FilteredModelAttributes } from '@server/types/index.js'
|
import { FilteredModelAttributes } from '@server/types/index.js'
|
||||||
import { MChannelId, MStreamingPlaylistVideo, MVideo, MVideoId, isStreamingPlaylist } from '@server/types/models/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 { decode as magnetUriDecode } from 'magnet-uri'
|
||||||
import { basename, extname } from 'path'
|
import { basename, extname } from 'path'
|
||||||
import { getDurationFromActivityStream } from '../../activity.js'
|
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)
|
let validIcons = videoObject.icon.filter(i => i.width > THUMBNAILS_SIZE.minWidth)
|
||||||
// Fallback if there are not valid icons
|
// Fallback if there are not valid icons
|
||||||
if (validIcons.length === 0) validIcons = videoObject.icon
|
if (validIcons.length === 0) validIcons = videoObject.icon
|
||||||
|
@ -38,19 +36,19 @@ function getThumbnailFromIcons (videoObject: VideoObject) {
|
||||||
return minBy(validIcons, 'width')
|
return minBy(validIcons, 'width')
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPreviewFromIcons (videoObject: VideoObject) {
|
export function getPreviewFromIcons (videoObject: VideoObject) {
|
||||||
const validIcons = videoObject.icon.filter(i => i.width > PREVIEWS_SIZE.minWidth)
|
const validIcons = videoObject.icon.filter(i => i.width > PREVIEWS_SIZE.minWidth)
|
||||||
|
|
||||||
return maxBy(validIcons, 'width')
|
return maxBy(validIcons, 'width')
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTagsFromObject (videoObject: VideoObject) {
|
export function getTagsFromObject (videoObject: VideoObject) {
|
||||||
return videoObject.tag
|
return videoObject.tag
|
||||||
.filter(isAPHashTagObject)
|
.filter(isAPHashTagObject)
|
||||||
.map(t => t.name)
|
.map(t => t.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileAttributesFromUrl (
|
export function getFileAttributesFromUrl (
|
||||||
videoOrPlaylist: MVideo | MStreamingPlaylistVideo,
|
videoOrPlaylist: MVideo | MStreamingPlaylistVideo,
|
||||||
urls: (ActivityTagObject | ActivityUrlObject)[]
|
urls: (ActivityTagObject | ActivityUrlObject)[]
|
||||||
) {
|
) {
|
||||||
|
@ -117,7 +115,7 @@ function getFileAttributesFromUrl (
|
||||||
return attributes
|
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[]
|
const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[]
|
||||||
if (playlistUrls.length === 0) return []
|
if (playlistUrls.length === 0) return []
|
||||||
|
|
||||||
|
@ -154,7 +152,7 @@ function getStreamingPlaylistAttributesFromObject (video: MVideoId, videoObject:
|
||||||
return attributes
|
return attributes
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLiveAttributesFromObject (video: MVideoId, videoObject: VideoObject) {
|
export function getLiveAttributesFromObject (video: MVideoId, videoObject: VideoObject) {
|
||||||
return {
|
return {
|
||||||
saveReplay: videoObject.liveSaveReplay,
|
saveReplay: videoObject.liveSaveReplay,
|
||||||
permanentLive: videoObject.permanentLive,
|
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 => ({
|
return videoObject.subtitleLanguage.map(c => ({
|
||||||
videoId: video.id,
|
videoId: video.id,
|
||||||
filename: VideoCaptionModel.generateCaptionName(c.identifier),
|
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
|
if (!isArray(videoObject.preview)) return undefined
|
||||||
|
|
||||||
const storyboard = videoObject.preview.find(p => p.rel.includes('storyboard'))
|
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)
|
const privacy = to.includes(ACTIVITY_PUB.PUBLIC)
|
||||||
? VideoPrivacy.PUBLIC
|
? VideoPrivacy.PUBLIC
|
||||||
: VideoPrivacy.UNLISTED
|
: VideoPrivacy.UNLISTED
|
||||||
|
@ -247,23 +245,7 @@ function getVideoAttributesFromObject (videoChannel: MChannelId, videoObject: Vi
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
export {
|
|
||||||
getThumbnailFromIcons,
|
|
||||||
getPreviewFromIcons,
|
|
||||||
|
|
||||||
getTagsFromObject,
|
|
||||||
|
|
||||||
getFileAttributesFromUrl,
|
|
||||||
getStreamingPlaylistAttributesFromObject,
|
|
||||||
|
|
||||||
getLiveAttributesFromObject,
|
|
||||||
getCaptionAttributesFromObject,
|
|
||||||
getStoryboardAttributeFromObject,
|
|
||||||
|
|
||||||
getVideoAttributesFromObject
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function isAPVideoUrlObject (url: any): url is ActivityVideoUrlObject {
|
function isAPVideoUrlObject (url: any): url is ActivityVideoUrlObject {
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import maxBy from 'lodash-es/maxBy.js'
|
|
||||||
|
|
||||||
function getBiggestActorImage <T extends { width: number }> (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
|
|
||||||
}
|
|
|
@ -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 { HttpStatusCode } from '@peertube/peertube-models'
|
||||||
import express from 'express'
|
|
||||||
import { CONFIG } from '../../../initializers/config.js'
|
|
||||||
import { AccountModel } from '@server/models/account/account.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 { VideoChannelModel } from '@server/models/video/video-channel.js'
|
||||||
import { MAccountHost, MChannelHost } from '@server/types/models/index.js'
|
import { MAccountHost, MChannelHost } from '@server/types/models/index.js'
|
||||||
import { getBiggestActorImage } from '@server/lib/actor-image.js'
|
import express from 'express'
|
||||||
import { ActorImageModel } from '@server/models/actor/actor-image.js'
|
import { CONFIG } from '../../../initializers/config.js'
|
||||||
import { TagsHtml } from './tags-html.js'
|
|
||||||
import { PageHtml } from './page-html.js'
|
import { PageHtml } from './page-html.js'
|
||||||
|
import { TagsHtml } from './tags-html.js'
|
||||||
|
|
||||||
export class ActorHtml {
|
export class ActorHtml {
|
||||||
|
|
||||||
|
@ -60,7 +59,7 @@ export class ActorHtml {
|
||||||
const siteName = CONFIG.INSTANCE.NAME
|
const siteName = CONFIG.INSTANCE.NAME
|
||||||
const title = entity.getDisplayName()
|
const title = entity.getDisplayName()
|
||||||
|
|
||||||
const avatar = getBiggestActorImage(entity.Actor.Avatars)
|
const avatar = maxBy(entity.Actor.Avatars, 'width')
|
||||||
const image = {
|
const image = {
|
||||||
url: ActorImageModel.getImageUrl(avatar),
|
url: ActorImageModel.getImageUrl(avatar),
|
||||||
width: avatar?.width,
|
width: avatar?.width,
|
||||||
|
|
|
@ -13,7 +13,10 @@ export abstract class ActorExporter <T> extends AbstractUserExporter<T> {
|
||||||
|
|
||||||
name: actor.preferredUsername,
|
name: actor.preferredUsername,
|
||||||
|
|
||||||
avatars: this.exportActorImageJSON(actor.Avatars),
|
avatars: actor.hasImage(ActorImageType.AVATAR)
|
||||||
|
? this.exportActorImageJSON(actor.Avatars)
|
||||||
|
: [],
|
||||||
|
|
||||||
banners: actor.hasImage(ActorImageType.BANNER)
|
banners: actor.hasImage(ActorImageType.BANNER)
|
||||||
? this.exportActorImageJSON(actor.Banners)
|
? this.exportActorImageJSON(actor.Banners)
|
||||||
: []
|
: []
|
||||||
|
|
|
@ -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 { 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 { AttributesOnly } from '@peertube/peertube-typescript-utils'
|
||||||
import { activityPubContextify } from '@server/helpers/activity-pub-utils.js'
|
import { activityPubContextify } from '@server/helpers/activity-pub-utils.js'
|
||||||
import { getContextFilter } from '@server/lib/activitypub/context.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 { 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 {
|
import {
|
||||||
AllowNull,
|
AllowNull,
|
||||||
BelongsTo,
|
BelongsTo,
|
||||||
|
@ -33,16 +31,14 @@ import { isActivityPubUrlValid } from '../../helpers/custom-validators/activityp
|
||||||
import {
|
import {
|
||||||
ACTIVITY_PUB,
|
ACTIVITY_PUB,
|
||||||
ACTIVITY_PUB_ACTOR_TYPES,
|
ACTIVITY_PUB_ACTOR_TYPES,
|
||||||
CONSTRAINTS_FIELDS,
|
CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME,
|
||||||
MIMETYPES,
|
|
||||||
SERVER_ACTOR_NAME,
|
|
||||||
WEBSERVER
|
WEBSERVER
|
||||||
} from '../../initializers/constants.js'
|
} from '../../initializers/constants.js'
|
||||||
import {
|
import {
|
||||||
MActor,
|
MActor,
|
||||||
MActorAccountChannelId,
|
|
||||||
MActorAPAccount,
|
MActorAPAccount,
|
||||||
MActorAPChannel,
|
MActorAPChannel,
|
||||||
|
MActorAccountChannelId,
|
||||||
MActorFollowersUrl,
|
MActorFollowersUrl,
|
||||||
MActorFormattable,
|
MActorFormattable,
|
||||||
MActorFull,
|
MActorFull,
|
||||||
|
@ -61,7 +57,6 @@ import { VideoChannelModel } from '../video/video-channel.js'
|
||||||
import { VideoModel } from '../video/video.js'
|
import { VideoModel } from '../video/video.js'
|
||||||
import { ActorFollowModel } from './actor-follow.js'
|
import { ActorFollowModel } from './actor-follow.js'
|
||||||
import { ActorImageModel } from './actor-image.js'
|
import { ActorImageModel } from './actor-image.js'
|
||||||
import maxBy from 'lodash-es/maxBy.js'
|
|
||||||
|
|
||||||
enum ScopeNames {
|
enum ScopeNames {
|
||||||
FULL = 'FULL'
|
FULL = 'FULL'
|
||||||
|
@ -562,24 +557,15 @@ export class ActorModel extends SequelizeModel<ActorModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
toActivityPubObject (this: MActorAPChannel | MActorAPAccount, name: string) {
|
toActivityPubObject (this: MActorAPChannel | MActorAPAccount, name: string) {
|
||||||
let icon: ActivityIconObject[]
|
let icon: ActivityIconObject[] // Avatars
|
||||||
let image: ActivityIconObject
|
let image: ActivityIconObject[] // Banners
|
||||||
|
|
||||||
if (this.hasImage(ActorImageType.AVATAR)) {
|
if (this.hasImage(ActorImageType.AVATAR)) {
|
||||||
icon = this.Avatars.map(a => a.toActivityPubObject())
|
icon = this.Avatars.map(a => a.toActivityPubObject())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasImage(ActorImageType.BANNER)) {
|
if (this.hasImage(ActorImageType.BANNER)) {
|
||||||
const banner = getBiggestActorImage((this as MActorAPChannel).Banners)
|
image = (this as MActorAPChannel).Banners.map(b => b.toActivityPubObject())
|
||||||
const extension = getLowercaseExtension(banner.filename)
|
|
||||||
|
|
||||||
image = {
|
|
||||||
type: 'Image',
|
|
||||||
mediaType: MIMETYPES.IMAGE.EXT_MIMETYPE[extension],
|
|
||||||
height: banner.height,
|
|
||||||
width: banner.width,
|
|
||||||
url: ActorImageModel.getImageUrl(banner)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = {
|
const json = {
|
||||||
|
|
|
@ -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 { UserNotification, type UserNotificationType_Type } from '@peertube/peertube-models'
|
||||||
import { uuidToShort } from '@peertube/peertube-node-utils'
|
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 { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user/index.js'
|
||||||
import { ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
|
import { ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
|
||||||
import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Table, UpdatedAt } from 'sequelize-typescript'
|
import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Table, UpdatedAt } from 'sequelize-typescript'
|
||||||
|
@ -518,7 +517,7 @@ export class UserNotificationModel extends SequelizeModel<UserNotificationModel>
|
||||||
if (!avatars || avatars.length === 0) return { avatar: undefined, avatars: [] }
|
if (!avatars || avatars.length === 0) return { avatar: undefined, avatars: [] }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
avatar: this.formatAvatar(getBiggestActorImage(avatars)),
|
avatar: this.formatAvatar(maxBy(avatars, 'width')),
|
||||||
|
|
||||||
avatars: avatars.map(a => this.formatAvatar(a))
|
avatars: avatars.map(a => this.formatAvatar(a))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { ffprobePromise, getAudioStream, getVideoStreamDimensionsInfo, getVideoStreamFPS, hasAudioStream } from '@peertube/peertube-ffmpeg'
|
||||||
import {
|
import {
|
||||||
FileStorage,
|
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 { MVideoSource } from '@server/types/models/video/video-source.js'
|
||||||
import Bluebird from 'bluebird'
|
import Bluebird from 'bluebird'
|
||||||
import { remove } from 'fs-extra/esm'
|
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 { FindOptions, IncludeOptions, Includeable, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
|
||||||
import {
|
import {
|
||||||
AfterCreate,
|
AfterCreate,
|
||||||
|
@ -1711,9 +1709,9 @@ export class VideoModel extends SequelizeModel<VideoModel> {
|
||||||
return this.VideoChannel.Account.Actor.Server?.isBlocked() || this.VideoChannel.Account.isBlocked()
|
return this.VideoChannel.Account.Actor.Server?.isBlocked() || this.VideoChannel.Account.isBlocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
getQualityFileBy<T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) {
|
getQualityFileBy<T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], property: 'resolution') => MVideoFile) {
|
||||||
const files = this.getAllFiles()
|
const files = this.getAllFiles()
|
||||||
const file = fun(files, file => file.resolution)
|
const file = fun(files, 'resolution')
|
||||||
if (!file) return undefined
|
if (!file) return undefined
|
||||||
|
|
||||||
if (file.videoId) {
|
if (file.videoId) {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { maxBy, minBy } from '@peertube/peertube-core-utils'
|
||||||
import { ActorImageType } from '@peertube/peertube-models'
|
import { ActorImageType } from '@peertube/peertube-models'
|
||||||
import { buildUUID, getLowercaseExtension } from '@peertube/peertube-node-utils'
|
import { buildUUID, getLowercaseExtension } from '@peertube/peertube-node-utils'
|
||||||
import { getImageSize, processImage } from '@server/helpers/image-utils.js'
|
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 { initDatabaseModels } from '@server/initializers/database.js'
|
||||||
import { updateActorImages } from '@server/lib/activitypub/actors/index.js'
|
import { updateActorImages } from '@server/lib/activitypub/actors/index.js'
|
||||||
import { sendUpdateActor } from '@server/lib/activitypub/send/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 { JobQueue } from '@server/lib/job-queue/index.js'
|
||||||
import { AccountModel } from '@server/models/account/account.js'
|
import { AccountModel } from '@server/models/account/account.js'
|
||||||
import { ActorModel } from '@server/models/actor/actor.js'
|
import { ActorModel } from '@server/models/actor/actor.js'
|
||||||
import { VideoChannelModel } from '@server/models/video/video-channel.js'
|
import { VideoChannelModel } from '@server/models/video/video-channel.js'
|
||||||
import { MAccountDefault, MActorDefault, MChannelDefault } from '@server/types/models/index.js'
|
import { MAccountDefault, MActorDefault, MChannelDefault } from '@server/types/models/index.js'
|
||||||
import minBy from 'lodash-es/minBy.js'
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
||||||
run()
|
run()
|
||||||
|
@ -100,7 +99,7 @@ async function generateSmallerAvatarIfNeeded (accountOrChannel: MAccountDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateSmallerAvatar (actor: MActorDefault) {
|
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 imageSize = minBy(ACTOR_IMAGES_SIZE[ActorImageType.AVATAR], 'width')
|
||||||
const sourceFilename = bigAvatar.filename
|
const sourceFilename = bigAvatar.filename
|
||||||
|
|
Loading…
Reference in New Issue