aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/video')
-rw-r--r--server/models/video/video.ts198
1 files changed, 105 insertions, 93 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 6fb5ececa..86316653f 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -113,7 +113,7 @@ const indexes: Sequelize.DefineIndexesOptions[] = [
113 unique: true 113 unique: true
114 }, 114 },
115 { 115 {
116 fields: [ 'url'], 116 fields: [ 'url' ],
117 unique: true 117 unique: true
118 } 118 }
119] 119]
@@ -150,7 +150,7 @@ type AvailableForListIDsOptions = {
150} 150}
151 151
152@Scopes({ 152@Scopes({
153 [ScopeNames.FOR_API]: (options: ForAPIOptions) => { 153 [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => {
154 const accountInclude = { 154 const accountInclude = {
155 attributes: [ 'id', 'name' ], 155 attributes: [ 'id', 'name' ],
156 model: AccountModel.unscoped(), 156 model: AccountModel.unscoped(),
@@ -203,7 +203,7 @@ type AvailableForListIDsOptions = {
203 const query: IFindOptions<VideoModel> = { 203 const query: IFindOptions<VideoModel> = {
204 where: { 204 where: {
205 id: { 205 id: {
206 [Sequelize.Op.any]: options.ids 206 [ Sequelize.Op.any ]: options.ids
207 } 207 }
208 }, 208 },
209 include: [ videoChannelInclude ] 209 include: [ videoChannelInclude ]
@@ -218,12 +218,12 @@ type AvailableForListIDsOptions = {
218 218
219 return query 219 return query
220 }, 220 },
221 [ScopeNames.AVAILABLE_FOR_LIST_IDS]: (options: AvailableForListIDsOptions) => { 221 [ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => {
222 const query: IFindOptions<VideoModel> = { 222 const query: IFindOptions<VideoModel> = {
223 attributes: [ 'id' ], 223 attributes: [ 'id' ],
224 where: { 224 where: {
225 id: { 225 id: {
226 [Sequelize.Op.and]: [ 226 [ Sequelize.Op.and ]: [
227 { 227 {
228 [ Sequelize.Op.notIn ]: Sequelize.literal( 228 [ Sequelize.Op.notIn ]: Sequelize.literal(
229 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' 229 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
@@ -246,7 +246,7 @@ type AvailableForListIDsOptions = {
246 } 246 }
247 ] 247 ]
248 }, 248 },
249 include: [ ] 249 include: []
250 } 250 }
251 251
252 if (options.filter || options.accountId || options.videoChannelId) { 252 if (options.filter || options.accountId || options.videoChannelId) {
@@ -303,27 +303,27 @@ type AvailableForListIDsOptions = {
303 303
304 // Force actorId to be a number to avoid SQL injections 304 // Force actorId to be a number to avoid SQL injections
305 const actorIdNumber = parseInt(options.actorId.toString(), 10) 305 const actorIdNumber = parseInt(options.actorId.toString(), 10)
306 query.where['id'][Sequelize.Op.and].push({ 306 query.where[ 'id' ][ Sequelize.Op.and ].push({
307 [ Sequelize.Op.in ]: Sequelize.literal( 307 [ Sequelize.Op.in ]: Sequelize.literal(
308 '(' + 308 '(' +
309 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + 309 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' +
310 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + 310 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
311 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + 311 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
312 ' UNION ALL ' + 312 ' UNION ALL ' +
313 'SELECT "video"."id" AS "id" FROM "video" ' + 313 'SELECT "video"."id" AS "id" FROM "video" ' +
314 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 314 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
315 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' + 315 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' +
316 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' + 316 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' +
317 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' + 317 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' +
318 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + 318 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
319 localVideosReq + 319 localVideosReq +
320 ')' 320 ')'
321 ) 321 )
322 }) 322 })
323 } 323 }
324 324
325 if (options.withFiles === true) { 325 if (options.withFiles === true) {
326 query.where['id'][Sequelize.Op.and].push({ 326 query.where[ 'id' ][ Sequelize.Op.and ].push({
327 [ Sequelize.Op.in ]: Sequelize.literal( 327 [ Sequelize.Op.in ]: Sequelize.literal(
328 '(SELECT "videoId" FROM "videoFile")' 328 '(SELECT "videoId" FROM "videoFile")'
329 ) 329 )
@@ -338,8 +338,8 @@ type AvailableForListIDsOptions = {
338 } 338 }
339 339
340 if (options.tagsOneOf) { 340 if (options.tagsOneOf) {
341 query.where['id'][Sequelize.Op.and].push({ 341 query.where[ 'id' ][ Sequelize.Op.and ].push({
342 [Sequelize.Op.in]: Sequelize.literal( 342 [ Sequelize.Op.in ]: Sequelize.literal(
343 '(' + 343 '(' +
344 'SELECT "videoId" FROM "videoTag" ' + 344 'SELECT "videoId" FROM "videoTag" ' +
345 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 345 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
@@ -350,8 +350,8 @@ type AvailableForListIDsOptions = {
350 } 350 }
351 351
352 if (options.tagsAllOf) { 352 if (options.tagsAllOf) {
353 query.where['id'][Sequelize.Op.and].push({ 353 query.where[ 'id' ][ Sequelize.Op.and ].push({
354 [Sequelize.Op.in]: Sequelize.literal( 354 [ Sequelize.Op.in ]: Sequelize.literal(
355 '(' + 355 '(' +
356 'SELECT "videoId" FROM "videoTag" ' + 356 'SELECT "videoId" FROM "videoTag" ' +
357 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 357 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
@@ -364,24 +364,24 @@ type AvailableForListIDsOptions = {
364 } 364 }
365 365
366 if (options.nsfw === true || options.nsfw === false) { 366 if (options.nsfw === true || options.nsfw === false) {
367 query.where['nsfw'] = options.nsfw 367 query.where[ 'nsfw' ] = options.nsfw
368 } 368 }
369 369
370 if (options.categoryOneOf) { 370 if (options.categoryOneOf) {
371 query.where['category'] = { 371 query.where[ 'category' ] = {
372 [Sequelize.Op.or]: options.categoryOneOf 372 [ Sequelize.Op.or ]: options.categoryOneOf
373 } 373 }
374 } 374 }
375 375
376 if (options.licenceOneOf) { 376 if (options.licenceOneOf) {
377 query.where['licence'] = { 377 query.where[ 'licence' ] = {
378 [Sequelize.Op.or]: options.licenceOneOf 378 [ Sequelize.Op.or ]: options.licenceOneOf
379 } 379 }
380 } 380 }
381 381
382 if (options.languageOneOf) { 382 if (options.languageOneOf) {
383 query.where['language'] = { 383 query.where[ 'language' ] = {
384 [Sequelize.Op.or]: options.languageOneOf 384 [ Sequelize.Op.or ]: options.languageOneOf
385 } 385 }
386 } 386 }
387 387
@@ -402,7 +402,7 @@ type AvailableForListIDsOptions = {
402 402
403 return query 403 return query
404 }, 404 },
405 [ScopeNames.WITH_ACCOUNT_DETAILS]: { 405 [ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
406 include: [ 406 include: [
407 { 407 {
408 model: () => VideoChannelModel.unscoped(), 408 model: () => VideoChannelModel.unscoped(),
@@ -454,10 +454,10 @@ type AvailableForListIDsOptions = {
454 } 454 }
455 ] 455 ]
456 }, 456 },
457 [ScopeNames.WITH_TAGS]: { 457 [ ScopeNames.WITH_TAGS ]: {
458 include: [ () => TagModel ] 458 include: [ () => TagModel ]
459 }, 459 },
460 [ScopeNames.WITH_BLACKLISTED]: { 460 [ ScopeNames.WITH_BLACKLISTED ]: {
461 include: [ 461 include: [
462 { 462 {
463 attributes: [ 'id', 'reason' ], 463 attributes: [ 'id', 'reason' ],
@@ -466,7 +466,7 @@ type AvailableForListIDsOptions = {
466 } 466 }
467 ] 467 ]
468 }, 468 },
469 [ScopeNames.WITH_FILES]: { 469 [ ScopeNames.WITH_FILES ]: {
470 include: [ 470 include: [
471 { 471 {
472 model: () => VideoFileModel.unscoped(), 472 model: () => VideoFileModel.unscoped(),
@@ -474,7 +474,7 @@ type AvailableForListIDsOptions = {
474 } 474 }
475 ] 475 ]
476 }, 476 },
477 [ScopeNames.WITH_SCHEDULED_UPDATE]: { 477 [ ScopeNames.WITH_SCHEDULED_UPDATE ]: {
478 include: [ 478 include: [
479 { 479 {
480 model: () => ScheduleVideoUpdateModel.unscoped(), 480 model: () => ScheduleVideoUpdateModel.unscoped(),
@@ -700,7 +700,7 @@ export class VideoModel extends Model<VideoModel> {
700 }, 700 },
701 onDelete: 'cascade', 701 onDelete: 'cascade',
702 hooks: true, 702 hooks: true,
703 ['separate' as any]: true 703 [ 'separate' as any ]: true
704 }) 704 })
705 VideoCaptions: VideoCaptionModel[] 705 VideoCaptions: VideoCaptionModel[]
706 706
@@ -749,9 +749,9 @@ export class VideoModel extends Model<VideoModel> {
749 749
750 // Do not wait video deletion because we could be in a transaction 750 // Do not wait video deletion because we could be in a transaction
751 Promise.all(tasks) 751 Promise.all(tasks)
752 .catch(err => { 752 .catch(err => {
753 logger.error('Some errors when removing files of video %s in before destroy hook.', instance.uuid, { err }) 753 logger.error('Some errors when removing files of video %s in before destroy hook.', instance.uuid, { err })
754 }) 754 })
755 755
756 return undefined 756 return undefined
757 } 757 }
@@ -783,9 +783,9 @@ export class VideoModel extends Model<VideoModel> {
783 order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ]), 783 order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ]),
784 where: { 784 where: {
785 id: { 785 id: {
786 [Sequelize.Op.in]: Sequelize.literal('(' + rawQuery + ')') 786 [ Sequelize.Op.in ]: Sequelize.literal('(' + rawQuery + ')')
787 }, 787 },
788 [Sequelize.Op.or]: [ 788 [ Sequelize.Op.or ]: [
789 { privacy: VideoPrivacy.PUBLIC }, 789 { privacy: VideoPrivacy.PUBLIC },
790 { privacy: VideoPrivacy.UNLISTED } 790 { privacy: VideoPrivacy.UNLISTED }
791 ] 791 ]
@@ -802,10 +802,10 @@ export class VideoModel extends Model<VideoModel> {
802 required: false, 802 required: false,
803 // We only want videos shared by this actor 803 // We only want videos shared by this actor
804 where: { 804 where: {
805 [Sequelize.Op.and]: [ 805 [ Sequelize.Op.and ]: [
806 { 806 {
807 id: { 807 id: {
808 [Sequelize.Op.not]: null 808 [ Sequelize.Op.not ]: null
809 } 809 }
810 }, 810 },
811 { 811 {
@@ -856,8 +856,8 @@ export class VideoModel extends Model<VideoModel> {
856 // totals: totalVideos + totalVideoShares 856 // totals: totalVideos + totalVideoShares
857 let totalVideos = 0 857 let totalVideos = 0
858 let totalVideoShares = 0 858 let totalVideoShares = 0
859 if (totals[0]) totalVideos = parseInt(totals[0].total, 10) 859 if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10)
860 if (totals[1]) totalVideoShares = parseInt(totals[1].total, 10) 860 if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10)
861 861
862 const total = totalVideos + totalVideoShares 862 const total = totalVideos + totalVideoShares
863 return { 863 return {
@@ -982,22 +982,22 @@ export class VideoModel extends Model<VideoModel> {
982 durationMin?: number // seconds 982 durationMin?: number // seconds
983 durationMax?: number // seconds 983 durationMax?: number // seconds
984 }) { 984 }) {
985 const whereAnd = [ ] 985 const whereAnd = []
986 986
987 if (options.startDate || options.endDate) { 987 if (options.startDate || options.endDate) {
988 const publishedAtRange = { } 988 const publishedAtRange = {}
989 989
990 if (options.startDate) publishedAtRange[Sequelize.Op.gte] = options.startDate 990 if (options.startDate) publishedAtRange[ Sequelize.Op.gte ] = options.startDate
991 if (options.endDate) publishedAtRange[Sequelize.Op.lte] = options.endDate 991 if (options.endDate) publishedAtRange[ Sequelize.Op.lte ] = options.endDate
992 992
993 whereAnd.push({ publishedAt: publishedAtRange }) 993 whereAnd.push({ publishedAt: publishedAtRange })
994 } 994 }
995 995
996 if (options.durationMin || options.durationMax) { 996 if (options.durationMin || options.durationMax) {
997 const durationRange = { } 997 const durationRange = {}
998 998
999 if (options.durationMin) durationRange[Sequelize.Op.gte] = options.durationMin 999 if (options.durationMin) durationRange[ Sequelize.Op.gte ] = options.durationMin
1000 if (options.durationMax) durationRange[Sequelize.Op.lte] = options.durationMax 1000 if (options.durationMax) durationRange[ Sequelize.Op.lte ] = options.durationMax
1001 1001
1002 whereAnd.push({ duration: durationRange }) 1002 whereAnd.push({ duration: durationRange })
1003 } 1003 }
@@ -1011,14 +1011,14 @@ export class VideoModel extends Model<VideoModel> {
1011 id: { 1011 id: {
1012 [ Sequelize.Op.in ]: Sequelize.literal( 1012 [ Sequelize.Op.in ]: Sequelize.literal(
1013 '(' + 1013 '(' +
1014 'SELECT "video"."id" FROM "video" ' + 1014 'SELECT "video"."id" FROM "video" ' +
1015 'WHERE ' + 1015 'WHERE ' +
1016 'lower(immutable_unaccent("video"."name")) % lower(immutable_unaccent(' + escapedSearch + ')) OR ' + 1016 'lower(immutable_unaccent("video"."name")) % lower(immutable_unaccent(' + escapedSearch + ')) OR ' +
1017 'lower(immutable_unaccent("video"."name")) LIKE lower(immutable_unaccent(' + escapedLikeSearch + '))' + 1017 'lower(immutable_unaccent("video"."name")) LIKE lower(immutable_unaccent(' + escapedLikeSearch + '))' +
1018 'UNION ALL ' + 1018 'UNION ALL ' +
1019 'SELECT "video"."id" FROM "video" LEFT JOIN "videoTag" ON "videoTag"."videoId" = "video"."id" ' + 1019 'SELECT "video"."id" FROM "video" LEFT JOIN "videoTag" ON "videoTag"."videoId" = "video"."id" ' +
1020 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 1020 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
1021 'WHERE "tag"."name" = ' + escapedSearch + 1021 'WHERE "tag"."name" = ' + escapedSearch +
1022 ')' 1022 ')'
1023 ) 1023 )
1024 } 1024 }
@@ -1167,11 +1167,11 @@ export class VideoModel extends Model<VideoModel> {
1167 limit: count, 1167 limit: count,
1168 group: field, 1168 group: field,
1169 having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), { 1169 having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), {
1170 [Sequelize.Op.gte]: threshold 1170 [ Sequelize.Op.gte ]: threshold
1171 }) as any, // FIXME: typings 1171 }) as any, // FIXME: typings
1172 where: { 1172 where: {
1173 [field]: { 1173 [ field ]: {
1174 [Sequelize.Op.not]: null 1174 [ Sequelize.Op.not ]: null
1175 }, 1175 },
1176 privacy: VideoPrivacy.PUBLIC, 1176 privacy: VideoPrivacy.PUBLIC,
1177 state: VideoState.PUBLISHED 1177 state: VideoState.PUBLISHED
@@ -1180,7 +1180,7 @@ export class VideoModel extends Model<VideoModel> {
1180 } 1180 }
1181 1181
1182 return VideoModel.findAll(query) 1182 return VideoModel.findAll(query)
1183 .then(rows => rows.map(r => r[field])) 1183 .then(rows => rows.map(r => r[ field ]))
1184 } 1184 }
1185 1185
1186 private static buildActorWhereWithFilter (filter?: VideoFilter) { 1186 private static buildActorWhereWithFilter (filter?: VideoFilter) {
@@ -1200,7 +1200,19 @@ export class VideoModel extends Model<VideoModel> {
1200 ] 1200 ]
1201 } 1201 }
1202 1202
1203 const { count, rows: rowsId } = await VideoModel.scope(idsScope).findAndCountAll(query) 1203 // Remove trending sort on count, because it uses a group by
1204 const countOptions = Object.assign({}, options, { trendingDays: undefined })
1205 const countQuery = Object.assign({}, query, { attributes: undefined, group: undefined })
1206 const countScope = {
1207 method: [
1208 ScopeNames.AVAILABLE_FOR_LIST_IDS, countOptions
1209 ]
1210 }
1211
1212 const [ count, rowsId ] = await Promise.all([
1213 VideoModel.scope(countScope).count(countQuery),
1214 VideoModel.scope(idsScope).findAll(query)
1215 ])
1204 const ids = rowsId.map(r => r.id) 1216 const ids = rowsId.map(r => r.id)
1205 1217
1206 if (ids.length === 0) return { data: [], total: count } 1218 if (ids.length === 0) return { data: [], total: count }
@@ -1228,23 +1240,23 @@ export class VideoModel extends Model<VideoModel> {
1228 } 1240 }
1229 1241
1230 private static getCategoryLabel (id: number) { 1242 private static getCategoryLabel (id: number) {
1231 return VIDEO_CATEGORIES[id] || 'Misc' 1243 return VIDEO_CATEGORIES[ id ] || 'Misc'
1232 } 1244 }
1233 1245
1234 private static getLicenceLabel (id: number) { 1246 private static getLicenceLabel (id: number) {
1235 return VIDEO_LICENCES[id] || 'Unknown' 1247 return VIDEO_LICENCES[ id ] || 'Unknown'
1236 } 1248 }
1237 1249
1238 private static getLanguageLabel (id: string) { 1250 private static getLanguageLabel (id: string) {
1239 return VIDEO_LANGUAGES[id] || 'Unknown' 1251 return VIDEO_LANGUAGES[ id ] || 'Unknown'
1240 } 1252 }
1241 1253
1242 private static getPrivacyLabel (id: number) { 1254 private static getPrivacyLabel (id: number) {
1243 return VIDEO_PRIVACIES[id] || 'Unknown' 1255 return VIDEO_PRIVACIES[ id ] || 'Unknown'
1244 } 1256 }
1245 1257
1246 private static getStateLabel (id: number) { 1258 private static getStateLabel (id: number) {
1247 return VIDEO_STATES[id] || 'Unknown' 1259 return VIDEO_STATES[ id ] || 'Unknown'
1248 } 1260 }
1249 1261
1250 getOriginalFile () { 1262 getOriginalFile () {
@@ -1466,28 +1478,28 @@ export class VideoModel extends Model<VideoModel> {
1466 const { baseUrlHttp, baseUrlWs } = this.getBaseUrls() 1478 const { baseUrlHttp, baseUrlWs } = this.getBaseUrls()
1467 1479
1468 return this.VideoFiles 1480 return this.VideoFiles
1469 .map(videoFile => { 1481 .map(videoFile => {
1470 let resolutionLabel = videoFile.resolution + 'p' 1482 let resolutionLabel = videoFile.resolution + 'p'
1471 1483
1472 return { 1484 return {
1473 resolution: { 1485 resolution: {
1474 id: videoFile.resolution, 1486 id: videoFile.resolution,
1475 label: resolutionLabel 1487 label: resolutionLabel
1476 }, 1488 },
1477 magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs), 1489 magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs),
1478 size: videoFile.size, 1490 size: videoFile.size,
1479 fps: videoFile.fps, 1491 fps: videoFile.fps,
1480 torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp), 1492 torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp),
1481 torrentDownloadUrl: this.getTorrentDownloadUrl(videoFile, baseUrlHttp), 1493 torrentDownloadUrl: this.getTorrentDownloadUrl(videoFile, baseUrlHttp),
1482 fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp), 1494 fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp),
1483 fileDownloadUrl: this.getVideoFileDownloadUrl(videoFile, baseUrlHttp) 1495 fileDownloadUrl: this.getVideoFileDownloadUrl(videoFile, baseUrlHttp)
1484 } as VideoFile 1496 } as VideoFile
1485 }) 1497 })
1486 .sort((a, b) => { 1498 .sort((a, b) => {
1487 if (a.resolution.id < b.resolution.id) return 1 1499 if (a.resolution.id < b.resolution.id) return 1
1488 if (a.resolution.id === b.resolution.id) return 0 1500 if (a.resolution.id === b.resolution.id) return 0
1489 return -1 1501 return -1
1490 }) 1502 })
1491 } 1503 }
1492 1504
1493 toActivityPubObject (): VideoTorrentObject { 1505 toActivityPubObject (): VideoTorrentObject {
@@ -1527,7 +1539,7 @@ export class VideoModel extends Model<VideoModel> {
1527 for (const file of this.VideoFiles) { 1539 for (const file of this.VideoFiles) {
1528 url.push({ 1540 url.push({
1529 type: 'Link', 1541 type: 'Link',
1530 mimeType: VIDEO_EXT_MIMETYPE[file.extname], 1542 mimeType: VIDEO_EXT_MIMETYPE[ file.extname ],
1531 href: this.getVideoFileUrl(file, baseUrlHttp), 1543 href: this.getVideoFileUrl(file, baseUrlHttp),
1532 height: file.resolution, 1544 height: file.resolution,
1533 size: file.size, 1545 size: file.size,