PeerTube/server/core/models/video/thumbnail.ts

241 lines
5.7 KiB
TypeScript
Raw Normal View History

2024-02-12 10:47:52 +01:00
import { ActivityIconObject, ThumbnailType, type ThumbnailType_Type } from '@peertube/peertube-models'
import { afterCommitIfTransaction } from '@server/helpers/database-utils.js'
2024-02-12 10:47:52 +01:00
import { MThumbnail, MThumbnailVideo, MVideo, MVideoPlaylist } from '@server/types/models/index.js'
import { remove } from 'fs-extra/esm'
import { join } from 'path'
import {
AfterDestroy,
AllowNull,
2021-02-16 08:50:40 +01:00
BeforeCreate,
BeforeUpdate,
BelongsTo,
Column,
CreatedAt,
DataType,
Default,
2024-02-22 10:12:04 +01:00
ForeignKey, Table,
UpdatedAt
} from 'sequelize-typescript'
import { logger } from '../../helpers/logger.js'
import { CONFIG } from '../../initializers/config.js'
import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, WEBSERVER } from '../../initializers/constants.js'
import { VideoPlaylistModel } from './video-playlist.js'
import { VideoModel } from './video.js'
2024-02-22 10:12:04 +01:00
import { SequelizeModel } from '../shared/sequelize-type.js'
@Table({
tableName: 'thumbnail',
indexes: [
{
fields: [ 'videoId' ]
},
{
fields: [ 'videoPlaylistId' ],
unique: true
},
{
fields: [ 'filename', 'type' ],
unique: true
}
]
})
2024-02-22 10:12:04 +01:00
export class ThumbnailModel extends SequelizeModel<ThumbnailModel> {
@AllowNull(false)
@Column
filename: string
@AllowNull(true)
@Default(null)
@Column
height: number
@AllowNull(true)
@Default(null)
@Column
width: number
@AllowNull(false)
@Column
type: ThumbnailType_Type
@AllowNull(true)
@Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max))
2019-04-24 10:28:57 +02:00
fileUrl: string
@AllowNull(true)
@Column
automaticallyGenerated: boolean
2023-06-06 15:59:51 +02:00
@AllowNull(false)
@Column
onDisk: boolean
@ForeignKey(() => VideoModel)
@Column
videoId: number
@BelongsTo(() => VideoModel, {
foreignKey: {
allowNull: true
},
onDelete: 'CASCADE'
})
Video: Awaited<VideoModel>
@ForeignKey(() => VideoPlaylistModel)
@Column
videoPlaylistId: number
@BelongsTo(() => VideoPlaylistModel, {
foreignKey: {
allowNull: true
},
onDelete: 'CASCADE'
})
VideoPlaylist: Awaited<VideoPlaylistModel>
@CreatedAt
createdAt: Date
@UpdatedAt
updatedAt: Date
2021-02-16 08:50:40 +01:00
// If this thumbnail replaced existing one, track the old name
previousThumbnailFilename: string
private static readonly types: { [ id in ThumbnailType_Type ]: { label: string, directory: string, staticPath: string } } = {
2019-04-23 09:50:57 +02:00
[ThumbnailType.MINIATURE]: {
label: 'miniature',
directory: CONFIG.STORAGE.THUMBNAILS_DIR,
staticPath: LAZY_STATIC_PATHS.THUMBNAILS
},
[ThumbnailType.PREVIEW]: {
label: 'preview',
directory: CONFIG.STORAGE.PREVIEWS_DIR,
2019-08-09 11:32:40 +02:00
staticPath: LAZY_STATIC_PATHS.PREVIEWS
}
}
2021-02-16 08:50:40 +01:00
@BeforeCreate
@BeforeUpdate
static removeOldFile (instance: ThumbnailModel, options) {
return afterCommitIfTransaction(options.transaction, () => instance.removePreviousFilenameIfNeeded())
}
@AfterDestroy
static removeFiles (instance: ThumbnailModel) {
logger.info('Removing %s file %s.', ThumbnailModel.types[instance.type].label, instance.filename)
// Don't block the transaction
instance.removeThumbnail()
2022-07-18 11:55:13 +02:00
.catch(err => logger.error('Cannot remove thumbnail file %s.', instance.filename, { err }))
}
static loadByFilename (filename: string, thumbnailType: ThumbnailType_Type): Promise<MThumbnail> {
2021-02-16 08:50:40 +01:00
const query = {
where: {
filename,
type: thumbnailType
}
}
return ThumbnailModel.findOne(query)
}
static loadWithVideoByFilename (filename: string, thumbnailType: ThumbnailType_Type): Promise<MThumbnailVideo> {
2019-08-09 15:04:36 +02:00
const query = {
where: {
filename,
type: thumbnailType
},
include: [
{
model: VideoModel.unscoped(),
required: true
}
]
2019-08-09 15:04:36 +02:00
}
return ThumbnailModel.findOne(query)
}
2024-06-05 14:43:41 +02:00
static listRemoteOnDisk () {
return this.findAll<MThumbnail>({
where: {
onDisk: true
},
include: [
{
attributes: [ 'id' ],
model: VideoModel.unscoped(),
required: true,
where: {
remote: true
}
}
]
})
}
// ---------------------------------------------------------------------------
static buildPath (type: ThumbnailType_Type, filename: string) {
2021-02-16 09:42:22 +01:00
const directory = ThumbnailModel.types[type].directory
return join(directory, filename)
}
2024-06-05 14:43:41 +02:00
// ---------------------------------------------------------------------------
2024-02-12 10:47:52 +01:00
getOriginFileUrl (videoOrPlaylist: MVideo | MVideoPlaylist) {
const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename
2024-02-12 10:47:52 +01:00
if (videoOrPlaylist.isOwned()) return WEBSERVER.URL + staticPath
2021-02-18 10:15:11 +01:00
return this.fileUrl
}
Add Podcast RSS feeds (#5487) * Initial test implementation of Podcast RSS This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option. I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort. * Update to pfeed-podcast 1.2.2 * Initial test implementation of Podcast RSS This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option. I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort. * Update to pfeed-podcast 1.2.2 * Initial test implementation of Podcast RSS This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option. I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort. * Update to pfeed-podcast 1.2.2 * Add correct feed image to RSS channel * Prefer HLS videos for podcast RSS Remove video/stream titles, add optional height attribute to podcast RSS * Prefix podcast RSS images with root server URL * Add optional video query support to include captions * Add transcripts & person images to podcast RSS feed * Prefer webseed/webtorrent files over HLS fragmented mp4s * Experimentally adding podcast fields to basic config page * Add validation for new basic config fields * Don't include "content" in podcast feed, use full description for "description" * Initial test implementation of Podcast RSS This is a pretty simple implementation to add support for The Podcast Namespace in RSS -- instead of affecting the existing RSS implementation, this adds a new UI option. I attempted to retain compatibility with the rest of the RSS feed implementation as much as possible and have created a temporary fork of the "pfeed" library to support this effort. * Update to pfeed-podcast 1.2.2 * Add correct feed image to RSS channel * Prefer HLS videos for podcast RSS Remove video/stream titles, add optional height attribute to podcast RSS * Prefix podcast RSS images with root server URL * Add optional video query support to include captions * Add transcripts & person images to podcast RSS feed * Prefer webseed/webtorrent files over HLS fragmented mp4s * Experimentally adding podcast fields to basic config page * Add validation for new basic config fields * Don't include "content" in podcast feed, use full description for "description" * Add medium/socialInteract to podcast RSS feeds. Use HTML for description * Change base production image to bullseye, install prosody in image * Add liveItem and trackers to Podcast RSS feeds Remove height from alternateEnclosure, replaced with title. * Clear Podcast RSS feed cache when live streams start/end * Upgrade to Node 16 * Refactor clearCacheRoute to use ApiCache * Remove unnecessary type hint * Update dockerfile to node 16, install python-is-python2 * Use new file paths for captions/playlists * Fix legacy videos in RSS after migration to object storage * Improve method of identifying non-fragmented mp4s in podcast RSS feeds * Don't include fragmented MP4s in podcast RSS feeds * Add experimental support for podcast:categories on the podcast RSS item * Fix undefined category when no videos exist Allows for empty feeds to exist (important for feeds that might only go live) * Add support for podcast:locked -- user has to opt in to show their email * Use comma for podcast:categories delimiter * Make cache clearing async * Fix merge, temporarily test with pfeed-podcast * Syntax changes * Add EXT_MIMETYPE constants for captions * Update & fix tests, fix enclosure mimetypes, remove admin email * Add test for podacst:socialInteract * Add filters hooks for podcast customTags * Remove showdown, updated to pfeed-podcast 6.1.2 * Add 'action:api.live-video.state.updated' hook * Avoid assigning undefined category to podcast feeds * Remove nvmrc * Remove comment * Remove unused podcast config * Remove more unused podcast config * Fix MChannelAccountDefault type hint missed in merge * Remove extra line * Re-add newline in config * Fix lint errors for isEmailPublic * Fix thumbnails in podcast feeds * Requested changes based on review * Provide podcast rss 2.0 only on video channels * Misc cleanup for a less messy PR * Lint fixes * Remove pfeed-podcast * Add peertube version to new hooks * Don't use query include, remove TODO * Remove film medium hack * Clear podcast rss cache before video/channel update hooks * Clear podcast rss cache before video uploaded/deleted hooks * Refactor podcast feed cache clearing * Set correct person name from video channel * Styling * Fix tests --------- Co-authored-by: Chocobozzz <me@florianbigard.com>
2023-05-22 16:00:05 +02:00
getLocalStaticPath () {
return ThumbnailModel.types[this.type].staticPath + this.filename
}
2019-05-16 16:55:34 +02:00
getPath () {
2021-02-16 09:42:22 +01:00
return ThumbnailModel.buildPath(this.type, this.filename)
2019-05-16 16:55:34 +02:00
}
2021-02-16 08:50:40 +01:00
getPreviousPath () {
2021-02-16 09:42:22 +01:00
return ThumbnailModel.buildPath(this.type, this.previousThumbnailFilename)
2021-02-16 08:50:40 +01:00
}
2019-05-16 16:55:34 +02:00
removeThumbnail () {
return remove(this.getPath())
}
2021-02-16 08:50:40 +01:00
removePreviousFilenameIfNeeded () {
if (!this.previousThumbnailFilename) return
const previousPath = this.getPreviousPath()
remove(previousPath)
.catch(err => logger.error('Cannot remove previous thumbnail file %s.', previousPath, { err }))
this.previousThumbnailFilename = undefined
}
isOwned () {
return !this.fileUrl
}
2024-02-12 10:47:52 +01:00
// ---------------------------------------------------------------------------
toActivityPubObject (this: MThumbnail, video: MVideo): ActivityIconObject {
return {
type: 'Image',
url: this.getOriginFileUrl(video),
mediaType: 'image/jpeg',
width: this.width,
height: this.height
}
}
}