]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video/thumbnail.ts
Generate a name for thumbnails
[github/Chocobozzz/PeerTube.git] / server / models / video / thumbnail.ts
1 import { remove } from 'fs-extra'
2 import { join } from 'path'
3 import {
4 AfterDestroy,
5 AllowNull,
6 BelongsTo,
7 Column,
8 CreatedAt,
9 DataType,
10 Default,
11 ForeignKey,
12 Model,
13 Table,
14 UpdatedAt
15 } from 'sequelize-typescript'
16 import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
17 import { MThumbnailVideo, MVideoAccountLight } from '@server/types/models'
18 import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
19 import { logger } from '../../helpers/logger'
20 import { CONFIG } from '../../initializers/config'
21 import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, STATIC_PATHS, WEBSERVER } from '../../initializers/constants'
22 import { VideoModel } from './video'
23 import { VideoPlaylistModel } from './video-playlist'
24
25 @Table({
26 tableName: 'thumbnail',
27 indexes: [
28 {
29 fields: [ 'videoId' ]
30 },
31 {
32 fields: [ 'videoPlaylistId' ],
33 unique: true
34 },
35 {
36 fields: [ 'filename', 'type' ],
37 unique: true
38 }
39 ]
40 })
41 export class ThumbnailModel extends Model {
42
43 @AllowNull(false)
44 @Column
45 filename: string
46
47 @AllowNull(true)
48 @Default(null)
49 @Column
50 height: number
51
52 @AllowNull(true)
53 @Default(null)
54 @Column
55 width: number
56
57 @AllowNull(false)
58 @Column
59 type: ThumbnailType
60
61 @AllowNull(true)
62 @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max))
63 fileUrl: string
64
65 @AllowNull(true)
66 @Column
67 automaticallyGenerated: boolean
68
69 @ForeignKey(() => VideoModel)
70 @Column
71 videoId: number
72
73 @BelongsTo(() => VideoModel, {
74 foreignKey: {
75 allowNull: true
76 },
77 onDelete: 'CASCADE'
78 })
79 Video: VideoModel
80
81 @ForeignKey(() => VideoPlaylistModel)
82 @Column
83 videoPlaylistId: number
84
85 @BelongsTo(() => VideoPlaylistModel, {
86 foreignKey: {
87 allowNull: true
88 },
89 onDelete: 'CASCADE'
90 })
91 VideoPlaylist: VideoPlaylistModel
92
93 @CreatedAt
94 createdAt: Date
95
96 @UpdatedAt
97 updatedAt: Date
98
99 private static readonly types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = {
100 [ThumbnailType.MINIATURE]: {
101 label: 'miniature',
102 directory: CONFIG.STORAGE.THUMBNAILS_DIR,
103 staticPath: STATIC_PATHS.THUMBNAILS
104 },
105 [ThumbnailType.PREVIEW]: {
106 label: 'preview',
107 directory: CONFIG.STORAGE.PREVIEWS_DIR,
108 staticPath: LAZY_STATIC_PATHS.PREVIEWS
109 }
110 }
111
112 @AfterDestroy
113 static removeFiles (instance: ThumbnailModel) {
114 logger.info('Removing %s file %s.', ThumbnailModel.types[instance.type].label, instance.filename)
115
116 // Don't block the transaction
117 instance.removeThumbnail()
118 .catch(err => logger.error('Cannot remove thumbnail file %s.', instance.filename, err))
119 }
120
121 static loadWithVideoByName (filename: string, thumbnailType: ThumbnailType): Promise<MThumbnailVideo> {
122 const query = {
123 where: {
124 filename,
125 type: thumbnailType
126 },
127 include: [
128 {
129 model: VideoModel.unscoped(),
130 required: true
131 }
132 ]
133 }
134
135 return ThumbnailModel.findOne(query)
136 }
137
138 getFileUrl (video: MVideoAccountLight) {
139 const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename
140
141 if (video.isOwned()) return WEBSERVER.URL + staticPath
142 if (this.fileUrl) return this.fileUrl
143
144 // Fallback if we don't have a file URL
145 return buildRemoteVideoBaseUrl(video, staticPath)
146 }
147
148 getPath () {
149 const directory = ThumbnailModel.types[this.type].directory
150 return join(directory, this.filename)
151 }
152
153 removeThumbnail () {
154 return remove(this.getPath())
155 }
156 }