-
diff --git a/client/src/app/shared/forms/reactive-file.component.scss b/client/src/app/shared/forms/reactive-file.component.scss
index d89844264..84c23c1d6 100644
--- a/client/src/app/shared/forms/reactive-file.component.scss
+++ b/client/src/app/shared/forms/reactive-file.component.scss
@@ -8,13 +8,11 @@
.button-file {
@include peertube-button-file(auto);
+ @include grey-button;
- min-width: 190px;
- }
-
- .file-constraints {
- margin-left: 5px;
- font-size: 13px;
+ &.with-icon {
+ @include button-with-icon;
+ }
}
.filename {
diff --git a/client/src/app/shared/forms/reactive-file.component.ts b/client/src/app/shared/forms/reactive-file.component.ts
index f60c38e8d..b7a821d4f 100644
--- a/client/src/app/shared/forms/reactive-file.component.ts
+++ b/client/src/app/shared/forms/reactive-file.component.ts
@@ -2,6 +2,7 @@ import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@ang
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { Notifier } from '@app/core'
import { I18n } from '@ngx-translate/i18n-polyfill'
+import { GlobalIconName } from '@app/shared/images/global-icon.component'
@Component({
selector: 'my-reactive-file',
@@ -21,6 +22,7 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor {
@Input() extensions: string[] = []
@Input() maxFileSize: number
@Input() displayFilename = false
+ @Input() icon: GlobalIconName
@Output() fileChanged = new EventEmitter
()
diff --git a/client/src/app/shared/images/image-upload.component.html b/client/src/app/shared/images/image-upload.component.html
deleted file mode 100644
index c09c862c4..000000000
--- a/client/src/app/shared/images/image-upload.component.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
![]()
-
-
diff --git a/client/src/app/shared/images/image-upload.component.scss b/client/src/app/shared/images/image-upload.component.scss
deleted file mode 100644
index b63963bca..000000000
--- a/client/src/app/shared/images/image-upload.component.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-@import '_variables';
-@import '_mixins';
-
-.root {
- height: auto;
- display: flex;
- align-items: center;
-
- .preview {
- border: 2px solid grey;
- border-radius: 4px;
- margin-left: 50px;
-
- &.no-image {
- background-color: #ececec;
- }
- }
-}
diff --git a/client/src/app/shared/images/preview-upload.component.html b/client/src/app/shared/images/preview-upload.component.html
new file mode 100644
index 000000000..5e1d5211b
--- /dev/null
+++ b/client/src/app/shared/images/preview-upload.component.html
@@ -0,0 +1,13 @@
+
+
+
+
+
![]()
+
+
+
+
(extensions: {{ allowedExtensionsMessage }}, max size: {{ maxVideoImageSize | bytes }})
+
diff --git a/client/src/app/shared/images/preview-upload.component.scss b/client/src/app/shared/images/preview-upload.component.scss
new file mode 100644
index 000000000..257060239
--- /dev/null
+++ b/client/src/app/shared/images/preview-upload.component.scss
@@ -0,0 +1,27 @@
+@import '_variables';
+@import '_mixins';
+
+.root {
+ height: auto;
+ display: flex;
+ flex-direction: column;
+
+ .preview-container {
+ position: relative;
+
+ my-reactive-file {
+ position: absolute;
+ bottom: 10px;
+ left: 10px;
+ }
+
+ .preview {
+ border: 2px solid grey;
+ border-radius: 4px;
+
+ &.no-image {
+ background-color: #ececec;
+ }
+ }
+ }
+}
diff --git a/client/src/app/shared/images/image-upload.component.ts b/client/src/app/shared/images/preview-upload.component.ts
similarity index 73%
rename from client/src/app/shared/images/image-upload.component.ts
rename to client/src/app/shared/images/preview-upload.component.ts
index 2da1592ff..44b78866e 100644
--- a/client/src/app/shared/images/image-upload.component.ts
+++ b/client/src/app/shared/images/preview-upload.component.ts
@@ -1,27 +1,28 @@
-import { Component, forwardRef, Input } from '@angular/core'
+import { Component, forwardRef, Input, OnInit } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
import { ServerService } from '@app/core'
@Component({
- selector: 'my-image-upload',
- styleUrls: [ './image-upload.component.scss' ],
- templateUrl: './image-upload.component.html',
+ selector: 'my-preview-upload',
+ styleUrls: [ './preview-upload.component.scss' ],
+ templateUrl: './preview-upload.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
- useExisting: forwardRef(() => ImageUploadComponent),
+ useExisting: forwardRef(() => PreviewUploadComponent),
multi: true
}
]
})
-export class ImageUploadComponent implements ControlValueAccessor {
+export class PreviewUploadComponent implements OnInit, ControlValueAccessor {
@Input() inputLabel: string
@Input() inputName: string
@Input() previewWidth: string
@Input() previewHeight: string
imageSrc: SafeResourceUrl
+ allowedExtensionsMessage = ''
private file: File
@@ -38,6 +39,10 @@ export class ImageUploadComponent implements ControlValueAccessor {
return this.serverService.getConfig().video.image.size.max
}
+ ngOnInit () {
+ this.allowedExtensionsMessage = this.videoImageExtensions.join(', ')
+ }
+
onFileChanged (file: File) {
this.file = file
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index ded65653f..39f1a69e2 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -69,7 +69,7 @@ import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/sha
import { ConfirmComponent } from '@app/shared/confirm/confirm.component'
import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component'
import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
-import { ImageUploadComponent } from '@app/shared/images/image-upload.component'
+import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component'
import { GlobalIconComponent } from '@app/shared/images/global-icon.component'
import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component'
import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component'
@@ -154,7 +154,7 @@ import { ClipboardModule } from 'ngx-clipboard'
ConfirmComponent,
GlobalIconComponent,
- ImageUploadComponent
+ PreviewUploadComponent
],
exports: [
@@ -218,7 +218,7 @@ import { ClipboardModule } from 'ngx-clipboard'
ConfirmComponent,
GlobalIconComponent,
- ImageUploadComponent,
+ PreviewUploadComponent,
NumberFormatterPipe,
ObjectLengthPipe,
diff --git a/client/src/app/shared/video/video-edit.model.ts b/client/src/app/shared/video/video-edit.model.ts
index 1f633d427..67d8e7711 100644
--- a/client/src/app/shared/video/video-edit.model.ts
+++ b/client/src/app/shared/video/video-edit.model.ts
@@ -85,6 +85,11 @@ export class VideoEdit implements VideoUpdate {
const originallyPublishedAt = new Date(values['originallyPublishedAt'])
this.originallyPublishedAt = originallyPublishedAt.toISOString()
}
+
+ // Use the same file than the preview for the thumbnail
+ if (this.previewfile) {
+ this.thumbnailfile = this.previewfile
+ }
}
toFormPatch () {
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html
index 99695204d..28572d611 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html
@@ -187,18 +187,14 @@
-
-
-
- Video preview
+
+
+ >
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.ts b/client/src/app/videos/+video-edit/shared/video-edit.component.ts
index c80efd802..95d397b52 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.ts
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts
@@ -100,7 +100,6 @@ export class VideoEditComponent implements OnInit, OnDestroy {
language: this.videoValidatorsService.VIDEO_LANGUAGE,
description: this.videoValidatorsService.VIDEO_DESCRIPTION,
tags: null,
- thumbnailfile: null,
previewfile: null,
support: this.videoValidatorsService.VIDEO_SUPPORT,
schedulePublicationAt: this.videoValidatorsService.VIDEO_SCHEDULE_PUBLICATION_AT,
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html
index 536769d2f..3247a2bd6 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html
+++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html
@@ -26,6 +26,27 @@
+
+
+
+
+
+
+
+
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss
index 8adf8f169..684342f09 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss
+++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss
@@ -1,9 +1,20 @@
@import 'variables';
@import 'mixins';
-.first-step-block .form-group-channel {
- margin-bottom: 20px;
- margin-top: 35px;
+.first-step-block {
+
+ .form-group-channel {
+ margin-bottom: 20px;
+ margin-top: 35px;
+ }
+
+ .audio-image-info {
+ margin-bottom: 10px;
+ }
+
+ .audio-preview {
+ margin: 30px 0;
+ }
}
.upload-progress-cancel {
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
index d6d4bad21..73de25c59 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
+++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
@@ -35,8 +35,10 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
userVideoQuotaUsed = 0
userVideoQuotaUsedDaily = 0
+ isUploadingAudioFile = false
isUploadingVideo = false
isUpdatingVideo = false
+
videoUploaded = false
videoUploadObservable: Subscription = null
videoUploadPercents = 0
@@ -44,7 +46,9 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
id: 0,
uuid: ''
}
+
waitTranscodingEnabled = true
+ previewfileUpload: File
error: string
@@ -100,6 +104,17 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
}
}
+ getVideoFile () {
+ return this.videofileInput.nativeElement.files[0]
+ }
+
+ getAudioUploadLabel () {
+ const videofile = this.getVideoFile()
+ if (!videofile) return this.i18n('Upload')
+
+ return this.i18n('Upload {{videofileName}}', { videofileName: videofile.name })
+ }
+
fileChange () {
this.uploadFirstStep()
}
@@ -114,38 +129,15 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
}
}
- uploadFirstStep () {
- const videofile = this.videofileInput.nativeElement.files[0]
+ uploadFirstStep (clickedOnButton = false) {
+ const videofile = this.getVideoFile()
if (!videofile) return
- // Check global user quota
- const bytePipes = new BytesPipe()
- const videoQuota = this.authService.getUser().videoQuota
- if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
- const msg = this.i18n(
- 'Your video quota is exceeded with this video (video size: {{videoSize}}, used: {{videoQuotaUsed}}, quota: {{videoQuota}})',
- {
- videoSize: bytePipes.transform(videofile.size, 0),
- videoQuotaUsed: bytePipes.transform(this.userVideoQuotaUsed, 0),
- videoQuota: bytePipes.transform(videoQuota, 0)
- }
- )
- this.notifier.error(msg)
- return
- }
+ if (!this.checkGlobalUserQuota(videofile)) return
+ if (!this.checkDailyUserQuota(videofile)) return
- // Check daily user quota
- const videoQuotaDaily = this.authService.getUser().videoQuotaDaily
- if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) {
- const msg = this.i18n(
- 'Your daily video quota is exceeded with this video (video size: {{videoSize}}, used: {{quotaUsedDaily}}, quota: {{quotaDaily}})',
- {
- videoSize: bytePipes.transform(videofile.size, 0),
- quotaUsedDaily: bytePipes.transform(this.userVideoQuotaUsedDaily, 0),
- quotaDaily: bytePipes.transform(videoQuotaDaily, 0)
- }
- )
- this.notifier.error(msg)
+ if (clickedOnButton === false && this.isAudioFile(videofile.name)) {
+ this.isUploadingAudioFile = true
return
}
@@ -180,6 +172,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
formData.append('channelId', '' + channelId)
formData.append('videofile', videofile)
+ if (this.previewfileUpload) {
+ formData.append('previewfile', this.previewfileUpload)
+ formData.append('thumbnailfile', this.previewfileUpload)
+ }
+
this.isUploadingVideo = true
this.firstStepDone.emit(name)
@@ -187,7 +184,8 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
name,
privacy,
nsfw,
- channelId
+ channelId,
+ previewfile: this.previewfileUpload
})
this.explainedVideoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies)
@@ -251,4 +249,52 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
}
)
}
+
+ private checkGlobalUserQuota (videofile: File) {
+ const bytePipes = new BytesPipe()
+
+ // Check global user quota
+ const videoQuota = this.authService.getUser().videoQuota
+ if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
+ const msg = this.i18n(
+ 'Your video quota is exceeded with this video (video size: {{videoSize}}, used: {{videoQuotaUsed}}, quota: {{videoQuota}})',
+ {
+ videoSize: bytePipes.transform(videofile.size, 0),
+ videoQuotaUsed: bytePipes.transform(this.userVideoQuotaUsed, 0),
+ videoQuota: bytePipes.transform(videoQuota, 0)
+ }
+ )
+ this.notifier.error(msg)
+
+ return false
+ }
+
+ return true
+ }
+
+ private checkDailyUserQuota (videofile: File) {
+ const bytePipes = new BytesPipe()
+
+ // Check daily user quota
+ const videoQuotaDaily = this.authService.getUser().videoQuotaDaily
+ if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) {
+ const msg = this.i18n(
+ 'Your daily video quota is exceeded with this video (video size: {{videoSize}}, used: {{quotaUsedDaily}}, quota: {{quotaDaily}})',
+ {
+ videoSize: bytePipes.transform(videofile.size, 0),
+ quotaUsedDaily: bytePipes.transform(this.userVideoQuotaUsedDaily, 0),
+ quotaDaily: bytePipes.transform(videoQuotaDaily, 0)
+ }
+ )
+ this.notifier.error(msg)
+
+ return false
+ }
+
+ return true
+ }
+
+ private isAudioFile (filename: string) {
+ return filename.endsWith('.mp3') || filename.endsWith('.flac') || filename.endsWith('.ogg')
+ }
}
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index 631504eab..55109dc32 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -545,8 +545,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
private flushPlayer () {
// Remove player if it exists
if (this.player) {
- this.player.dispose()
- this.player = undefined
+ try {
+ this.player.dispose()
+ this.player = undefined
+ } catch (err) {
+ console.error('Cannot dispose player.', err)
+ }
}
}
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index 6cdd54372..31cbc7dfd 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -117,8 +117,17 @@ export class PeertubePlayerManager {
videojs(options.common.playerElement, videojsOptions, function (this: any) {
const player = this
- player.tech_.one('error', () => self.maybeFallbackToWebTorrent(mode, player, options))
- player.one('error', () => self.maybeFallbackToWebTorrent(mode, player, options))
+ let alreadyFallback = false
+
+ player.tech_.one('error', () => {
+ if (!alreadyFallback) self.maybeFallbackToWebTorrent(mode, player, options)
+ alreadyFallback = true
+ })
+
+ player.one('error', () => {
+ if (!alreadyFallback) self.maybeFallbackToWebTorrent(mode, player, options)
+ alreadyFallback = true
+ })
self.addContextMenu(mode, player, options.common.embedUrl)
diff --git a/config/default.yaml b/config/default.yaml
index 27952d048..fcbbf17e8 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -180,6 +180,8 @@ transcoding:
enabled: true
# Allow your users to upload .mkv, .mov, .avi, .flv videos
allow_additional_extensions: true
+ # If a user uploads an audio file, PeerTube will create a video by merging the preview file and the audio file
+ allow_audio_files: true
threads: 1
resolutions: # Only created if the original video has a higher resolution, uses more storage!
240p: false
diff --git a/config/production.yaml.example b/config/production.yaml.example
index f84e15670..0ab99ac45 100644
--- a/config/production.yaml.example
+++ b/config/production.yaml.example
@@ -188,6 +188,8 @@ transcoding:
enabled: true
# Allow your users to upload .mkv, .mov, .avi, .flv videos
allow_additional_extensions: true
+ # If a user uploads an audio file, PeerTube will create a video by merging the preview file and the audio file
+ allow_audio_files: true
threads: 1
resolutions: # Only created if the original video has a higher resolution, uses more storage!
240p: false
diff --git a/config/test-2.yaml b/config/test-2.yaml
index a5515afa4..de7300366 100644
--- a/config/test-2.yaml
+++ b/config/test-2.yaml
@@ -31,3 +31,4 @@ signup:
transcoding:
enabled: true
allow_additional_extensions: true
+ allow_audio_files: true
diff --git a/config/test.yaml b/config/test.yaml
index 682530840..7dabe433c 100644
--- a/config/test.yaml
+++ b/config/test.yaml
@@ -55,6 +55,7 @@ signup:
transcoding:
enabled: true
allow_additional_extensions: false
+ allow_audio_files: false
threads: 2
resolutions:
240p: true
diff --git a/scripts/create-transcoding-job.ts b/scripts/create-transcoding-job.ts
index 4a677eacb..2b7cb5177 100755
--- a/scripts/create-transcoding-job.ts
+++ b/scripts/create-transcoding-job.ts
@@ -2,6 +2,7 @@ import * as program from 'commander'
import { VideoModel } from '../server/models/video/video'
import { initDatabaseModels } from '../server/initializers'
import { JobQueue } from '../server/lib/job-queue'
+import { VideoTranscodingPayload } from '../server/lib/job-queue/handlers/video-transcoding'
program
.option('-v, --video [videoUUID]', 'Video UUID')
@@ -31,15 +32,9 @@ async function run () {
const video = await VideoModel.loadByUUIDWithFile(program['video'])
if (!video) throw new Error('Video not found.')
- const dataInput = {
- videoUUID: video.uuid,
- isNewVideo: false,
- resolution: undefined
- }
-
- if (program.resolution !== undefined) {
- dataInput.resolution = program.resolution
- }
+ const dataInput: VideoTranscodingPayload = program.resolution !== undefined
+ ? { type: 'new-resolution' as 'new-resolution', videoUUID: video.uuid, isNewVideo: false, resolution: program.resolution }
+ : { type: 'optimize' as 'optimize', videoUUID: video.uuid, isNewVideo: false }
await JobQueue.Instance.init()
await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput })
diff --git a/server/assets/default-audio-background.jpg b/server/assets/default-audio-background.jpg
new file mode 100644
index 000000000..a19173eac
Binary files /dev/null and b/server/assets/default-audio-background.jpg differ
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 40012c03b..d9ce6a153 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -255,6 +255,7 @@ function customConfig (): CustomConfig {
transcoding: {
enabled: CONFIG.TRANSCODING.ENABLED,
allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
+ allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
threads: CONFIG.TRANSCODING.THREADS,
resolutions: {
'240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 1a18a8ae8..40a2c972b 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -6,7 +6,14 @@ import { logger } from '../../../helpers/logger'
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
-import { MIMETYPES, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants'
+import {
+ DEFAULT_AUDIO_RESOLUTION,
+ MIMETYPES,
+ VIDEO_CATEGORIES,
+ VIDEO_LANGUAGES,
+ VIDEO_LICENCES,
+ VIDEO_PRIVACIES
+} from '../../../initializers/constants'
import {
changeVideoChannelShare,
federateVideoIfNeeded,
@@ -54,6 +61,7 @@ import { CONFIG } from '../../../initializers/config'
import { sequelizeTypescript } from '../../../initializers/database'
import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail'
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
+import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding'
const auditLogger = auditLoggerFactory('videos')
const videosRouter = express.Router()
@@ -191,18 +199,19 @@ async function addVideo (req: express.Request, res: express.Response) {
const video = new VideoModel(videoData)
video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
- // Build the file object
- const { videoFileResolution } = await getVideoFileResolution(videoPhysicalFile.path)
- const fps = await getVideoFileFPS(videoPhysicalFile.path)
-
const videoFileData = {
extname: extname(videoPhysicalFile.filename),
- resolution: videoFileResolution,
- size: videoPhysicalFile.size,
- fps
+ size: videoPhysicalFile.size
}
const videoFile = new VideoFileModel(videoFileData)
+ if (!videoFile.isAudio()) {
+ videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path)
+ videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).videoFileResolution
+ } else {
+ videoFile.resolution = DEFAULT_AUDIO_RESOLUTION
+ }
+
// Move physical file
const videoDir = CONFIG.STORAGE.VIDEOS_DIR
const destination = join(videoDir, video.getVideoFilename(videoFile))
@@ -279,9 +288,21 @@ async function addVideo (req: express.Request, res: express.Response) {
if (video.state === VideoState.TO_TRANSCODE) {
// Put uuid because we don't have id auto incremented for now
- const dataInput = {
- videoUUID: videoCreated.uuid,
- isNewVideo: true
+ let dataInput: VideoTranscodingPayload
+
+ if (videoFile.isAudio()) {
+ dataInput = {
+ type: 'merge-audio' as 'merge-audio',
+ resolution: DEFAULT_AUDIO_RESOLUTION,
+ videoUUID: videoCreated.uuid,
+ isNewVideo: true
+ }
+ } else {
+ dataInput = {
+ type: 'optimize' as 'optimize',
+ videoUUID: videoCreated.uuid,
+ isNewVideo: true
+ }
}
await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput })
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 05019fcc2..d57dba6ce 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -181,7 +181,7 @@ async function getVideoCaption (req: express.Request, res: express.Response) {
return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE })
}
-async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function generateNodeinfo (req: express.Request, res: express.Response) {
const { totalVideos } = await VideoModel.getStats()
const { totalLocalVideoComments } = await VideoCommentModel.getStats()
const { totalUsers } = await UserModel.getStats()
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts
index e0a1d56a5..00f3f198b 100644
--- a/server/helpers/express-utils.ts
+++ b/server/helpers/express-utils.ts
@@ -74,7 +74,18 @@ function createReqFiles (
},
filename: async (req, file, cb) => {
- const extension = mimeTypes[ file.mimetype ] || extname(file.originalname)
+ let extension: string
+ const fileExtension = extname(file.originalname)
+ const extensionFromMimetype = mimeTypes[ file.mimetype ]
+
+ // Take the file extension if we don't understand the mime type
+ // We have the OGG/OGV exception too because firefox sends a bad mime type when sending an OGG file
+ if (fileExtension === '.ogg' || fileExtension === '.ogv' || !extensionFromMimetype) {
+ extension = fileExtension
+ } else {
+ extension = extensionFromMimetype
+ }
+
let randomString = ''
try {
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index 2fdf34cb7..c180da832 100644
--- a/server/helpers/ffmpeg-utils.ts
+++ b/server/helpers/ffmpeg-utils.ts
@@ -117,37 +117,50 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima
}
}
-type TranscodeOptions = {
+type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio'
+
+interface BaseTranscodeOptions {
+ type: TranscodeOptionsType
inputPath: string
outputPath: string
resolution: VideoResolution
isPortraitMode?: boolean
- doQuickTranscode?: Boolean
+}
- hlsPlaylist?: {
+interface HLSTranscodeOptions extends BaseTranscodeOptions {
+ type: 'hls'
+ hlsPlaylist: {
videoFilename: string
}
}
+interface QuickTranscodeOptions extends BaseTranscodeOptions {
+ type: 'quick-transcode'
+}
+
+interface VideoTranscodeOptions extends BaseTranscodeOptions {
+ type: 'video'
+}
+
+interface MergeAudioTranscodeOptions extends BaseTranscodeOptions {
+ type: 'merge-audio'
+ audioPath: string
+}
+
+type TranscodeOptions = HLSTranscodeOptions | VideoTranscodeOptions | MergeAudioTranscodeOptions | QuickTranscodeOptions
+
function transcode (options: TranscodeOptions) {
return new Promise