diff options
39 files changed, 268 insertions, 90 deletions
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index fd4d3d9c9..6ece7e8bc 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html | |||
@@ -220,6 +220,14 @@ | |||
220 | <ng-template [ngIf]="isTranscodingEnabled()"> | 220 | <ng-template [ngIf]="isTranscodingEnabled()"> |
221 | 221 | ||
222 | <div class="form-group"> | 222 | <div class="form-group"> |
223 | <my-peertube-checkbox | ||
224 | inputName="transcodingAllowAdditionalExtensions" formControlName="transcodingAllowAdditionalExtensions" | ||
225 | i18n-labelText labelText="Allow additional extensions" | ||
226 | i18n-helpHtml helpHtml="Allow your users to upload .mkv, .mov, .avi, .flv videos" | ||
227 | ></my-peertube-checkbox> | ||
228 | </div> | ||
229 | |||
230 | <div class="form-group"> | ||
223 | <label i18n for="transcodingThreads">Transcoding threads</label> | 231 | <label i18n for="transcodingThreads">Transcoding threads</label> |
224 | <div class="peertube-select-container"> | 232 | <div class="peertube-select-container"> |
225 | <select id="transcodingThreads" formControlName="transcodingThreads"> | 233 | <select id="transcodingThreads" formControlName="transcodingThreads"> |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index f48b6fc1a..6eea1cd76 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | |||
@@ -82,6 +82,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
82 | userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, | 82 | userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA, |
83 | userVideoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY, | 83 | userVideoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY, |
84 | transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS, | 84 | transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS, |
85 | transcodingAllowAdditionalExtensions: null, | ||
85 | transcodingEnabled: null, | 86 | transcodingEnabled: null, |
86 | customizationJavascript: null, | 87 | customizationJavascript: null, |
87 | customizationCSS: null | 88 | customizationCSS: null |
@@ -163,6 +164,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
163 | }, | 164 | }, |
164 | transcoding: { | 165 | transcoding: { |
165 | enabled: this.form.value['transcodingEnabled'], | 166 | enabled: this.form.value['transcodingEnabled'], |
167 | allowAdditionalExtensions: this.form.value['transcodingAllowAdditionalExtensions'], | ||
166 | threads: this.form.value['transcodingThreads'], | 168 | threads: this.form.value['transcodingThreads'], |
167 | resolutions: { | 169 | resolutions: { |
168 | '240p': this.form.value[this.getResolutionKey('240p')], | 170 | '240p': this.form.value[this.getResolutionKey('240p')], |
@@ -221,6 +223,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
221 | userVideoQuotaDaily: this.customConfig.user.videoQuotaDaily, | 223 | userVideoQuotaDaily: this.customConfig.user.videoQuotaDaily, |
222 | transcodingThreads: this.customConfig.transcoding.threads, | 224 | transcodingThreads: this.customConfig.transcoding.threads, |
223 | transcodingEnabled: this.customConfig.transcoding.enabled, | 225 | transcodingEnabled: this.customConfig.transcoding.enabled, |
226 | transcodingAllowAdditionalExtensions: this.customConfig.transcoding.allowAdditionalExtensions, | ||
224 | customizationJavascript: this.customConfig.instance.customizations.javascript, | 227 | customizationJavascript: this.customConfig.instance.customizations.javascript, |
225 | customizationCSS: this.customConfig.instance.customizations.css, | 228 | customizationCSS: this.customConfig.instance.customizations.css, |
226 | importVideosHttpEnabled: this.customConfig.import.videos.http.enabled, | 229 | importVideosHttpEnabled: this.customConfig.import.videos.http.enabled, |
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.html b/client/src/app/shared/forms/peertube-checkbox.component.html index fb3006b53..7b8bcf601 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.html +++ b/client/src/app/shared/forms/peertube-checkbox.component.html | |||
@@ -1,10 +1,10 @@ | |||
1 | <div class="root"> | 1 | <div class="root"> |
2 | <label class="form-group-checkbox"> | 2 | <label class="form-group-checkbox"> |
3 | <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="isDisabled" /> | 3 | <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="disabled" /> |
4 | <span role="checkbox" [attr.aria-checked]="checked"></span> | 4 | <span role="checkbox" [attr.aria-checked]="checked"></span> |
5 | <span *ngIf="labelText">{{ labelText }}</span> | 5 | <span *ngIf="labelText">{{ labelText }}</span> |
6 | <span *ngIf="labelHtml" [innerHTML]="labelHtml"></span> | 6 | <span *ngIf="labelHtml" [innerHTML]="labelHtml"></span> |
7 | </label> | 7 | </label> |
8 | 8 | ||
9 | <my-help *ngIf="helpHtml" tooltipPlacement="top" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help> | 9 | <my-help *ngIf="helpHtml" tooltipPlacement="top" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help> |
10 | </div> \ No newline at end of file | 10 | </div> |
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.ts b/client/src/app/shared/forms/peertube-checkbox.component.ts index bbc9904df..c1a6915e8 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.ts +++ b/client/src/app/shared/forms/peertube-checkbox.component.ts | |||
@@ -19,8 +19,7 @@ export class PeertubeCheckboxComponent implements ControlValueAccessor { | |||
19 | @Input() labelText: string | 19 | @Input() labelText: string |
20 | @Input() labelHtml: string | 20 | @Input() labelHtml: string |
21 | @Input() helpHtml: string | 21 | @Input() helpHtml: string |
22 | 22 | @Input() disabled = false | |
23 | isDisabled = false | ||
24 | 23 | ||
25 | propagateChange = (_: any) => { /* empty */ } | 24 | propagateChange = (_: any) => { /* empty */ } |
26 | 25 | ||
@@ -41,6 +40,6 @@ export class PeertubeCheckboxComponent implements ControlValueAccessor { | |||
41 | } | 40 | } |
42 | 41 | ||
43 | setDisabledState (isDisabled: boolean) { | 42 | setDisabledState (isDisabled: boolean) { |
44 | this.isDisabled = isDisabled | 43 | this.disabled = isDisabled |
45 | } | 44 | } |
46 | } | 45 | } |
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 33c766d87..bd52d686a 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 | |||
@@ -126,6 +126,7 @@ | |||
126 | ></my-peertube-checkbox> | 126 | ></my-peertube-checkbox> |
127 | 127 | ||
128 | <my-peertube-checkbox | 128 | <my-peertube-checkbox |
129 | *ngIf="waitTranscodingEnabled" | ||
129 | inputName="waitTranscoding" formControlName="waitTranscoding" | 130 | inputName="waitTranscoding" formControlName="waitTranscoding" |
130 | i18n-labelText labelText="Wait transcoding before publishing the video" | 131 | i18n-labelText labelText="Wait transcoding before publishing the video" |
131 | i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends." | 132 | i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends." |
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 a56733e57..1d35b4ba8 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 | |||
@@ -27,6 +27,7 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
27 | @Input() userVideoChannels: { id: number, label: string, support: string }[] = [] | 27 | @Input() userVideoChannels: { id: number, label: string, support: string }[] = [] |
28 | @Input() schedulePublicationPossible = true | 28 | @Input() schedulePublicationPossible = true |
29 | @Input() videoCaptions: VideoCaptionEdit[] = [] | 29 | @Input() videoCaptions: VideoCaptionEdit[] = [] |
30 | @Input() waitTranscodingEnabled = true | ||
30 | 31 | ||
31 | @ViewChild('videoCaptionAddModal') videoCaptionAddModal: VideoCaptionAddModalComponent | 32 | @ViewChild('videoCaptionAddModal') videoCaptionAddModal: VideoCaptionAddModalComponent |
32 | 33 | ||
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 a09f54dfc..289a28c66 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 | |||
@@ -6,7 +6,7 @@ | |||
6 | <span i18n>Select the file to upload</span> | 6 | <span i18n>Select the file to upload</span> |
7 | <input #videofileInput type="file" name="videofile" id="videofile" [accept]="videoExtensions" (change)="fileChange()" /> | 7 | <input #videofileInput type="file" name="videofile" id="videofile" [accept]="videoExtensions" (change)="fileChange()" /> |
8 | </div> | 8 | </div> |
9 | <span class="button-file-extension">(.mp4, .webm, .ogv)</span> | 9 | <span class="button-file-extension">({{ videoExtensions }})</span> |
10 | 10 | ||
11 | <div class="form-group form-group-channel"> | 11 | <div class="form-group form-group-channel"> |
12 | <label i18n for="first-step-channel">Channel</label> | 12 | <label i18n for="first-step-channel">Channel</label> |
@@ -47,6 +47,7 @@ | |||
47 | <my-video-edit | 47 | <my-video-edit |
48 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" | 48 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" |
49 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" | 49 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" |
50 | [waitTranscodingEnabled]="waitTranscodingEnabled" | ||
50 | ></my-video-edit> | 51 | ></my-video-edit> |
51 | 52 | ||
52 | <div class="submit-container"> | 53 | <div class="submit-container"> |
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 7ea3691fa..2180e22ab 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 | |||
@@ -44,6 +44,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
44 | id: 0, | 44 | id: 0, |
45 | uuid: '' | 45 | uuid: '' |
46 | } | 46 | } |
47 | waitTranscodingEnabled = true | ||
47 | 48 | ||
48 | error: string | 49 | error: string |
49 | 50 | ||
@@ -117,6 +118,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
117 | const videofile = this.videofileInput.nativeElement.files[0] | 118 | const videofile = this.videofileInput.nativeElement.files[0] |
118 | if (!videofile) return | 119 | if (!videofile) return |
119 | 120 | ||
121 | // Check global user quota | ||
120 | const bytePipes = new BytesPipe() | 122 | const bytePipes = new BytesPipe() |
121 | const videoQuota = this.authService.getUser().videoQuota | 123 | const videoQuota = this.authService.getUser().videoQuota |
122 | if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { | 124 | if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { |
@@ -132,6 +134,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
132 | return | 134 | return |
133 | } | 135 | } |
134 | 136 | ||
137 | // Check daily user quota | ||
135 | const videoQuotaDaily = this.authService.getUser().videoQuotaDaily | 138 | const videoQuotaDaily = this.authService.getUser().videoQuotaDaily |
136 | if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) { | 139 | if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) { |
137 | const msg = this.i18n( | 140 | const msg = this.i18n( |
@@ -146,6 +149,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
146 | return | 149 | return |
147 | } | 150 | } |
148 | 151 | ||
152 | // Build name field | ||
149 | const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '') | 153 | const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '') |
150 | let name: string | 154 | let name: string |
151 | 155 | ||
@@ -153,6 +157,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy | |||
153 | if (nameWithoutExtension.length < 3) name = videofile.name | 157 | if (nameWithoutExtension.length < 3) name = videofile.name |
154 | else name = nameWithoutExtension | 158 | else name = nameWithoutExtension |
155 | 159 | ||
160 | // Force user to wait transcoding for unsupported video types in web browsers | ||
161 | if (!videofile.name.endsWith('.mp4') && !videofile.name.endsWith('.webm') && !videofile.name.endsWith('.ogv')) { | ||
162 | this.waitTranscodingEnabled = false | ||
163 | } | ||
164 | |||
156 | const privacy = this.firstStepPrivacyId.toString() | 165 | const privacy = this.firstStepPrivacyId.toString() |
157 | const nsfw = false | 166 | const nsfw = false |
158 | const waitTranscoding = true | 167 | const waitTranscoding = true |
diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html index 9242c30a0..0457778c0 100644 --- a/client/src/app/videos/+video-edit/video-update.component.html +++ b/client/src/app/videos/+video-edit/video-update.component.html | |||
@@ -8,7 +8,7 @@ | |||
8 | <my-video-edit | 8 | <my-video-edit |
9 | [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible" | 9 | [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible" |
10 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" | 10 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" |
11 | [videoCaptions]="videoCaptions" | 11 | [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled" |
12 | ></my-video-edit> | 12 | ></my-video-edit> |
13 | 13 | ||
14 | <div class="submit-container"> | 14 | <div class="submit-container"> |
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts index 3a0f3a39a..d99a02b18 100644 --- a/client/src/app/videos/+video-edit/video-update.component.ts +++ b/client/src/app/videos/+video-edit/video-update.component.ts | |||
@@ -12,6 +12,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
12 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 12 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
13 | import { VideoCaptionService } from '@app/shared/video-caption' | 13 | import { VideoCaptionService } from '@app/shared/video-caption' |
14 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' | 14 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' |
15 | import { VideoDetails } from '@app/shared/video/video-details.model' | ||
15 | 16 | ||
16 | @Component({ | 17 | @Component({ |
17 | selector: 'my-videos-update', | 18 | selector: 'my-videos-update', |
@@ -26,6 +27,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
26 | userVideoChannels: { id: number, label: string, support: string }[] = [] | 27 | userVideoChannels: { id: number, label: string, support: string }[] = [] |
27 | schedulePublicationPossible = false | 28 | schedulePublicationPossible = false |
28 | videoCaptions: VideoCaptionEdit[] = [] | 29 | videoCaptions: VideoCaptionEdit[] = [] |
30 | waitTranscodingEnabled = true | ||
29 | 31 | ||
30 | private updateDone = false | 32 | private updateDone = false |
31 | 33 | ||
@@ -65,6 +67,11 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
65 | 67 | ||
66 | this.videoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies) | 68 | this.videoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies) |
67 | 69 | ||
70 | const videoFiles = (video as VideoDetails).files | ||
71 | if (videoFiles.length > 1) { // Already transcoded | ||
72 | this.waitTranscodingEnabled = false | ||
73 | } | ||
74 | |||
68 | // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout | 75 | // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout |
69 | setTimeout(() => this.hydrateFormFromVideo()) | 76 | setTimeout(() => this.hydrateFormFromVideo()) |
70 | }, | 77 | }, |
diff --git a/config/default.yaml b/config/default.yaml index d95fdc57b..080638a13 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -124,6 +124,8 @@ user: | |||
124 | # Please, do not disable transcoding since many uploaded videos will not work | 124 | # Please, do not disable transcoding since many uploaded videos will not work |
125 | transcoding: | 125 | transcoding: |
126 | enabled: true | 126 | enabled: true |
127 | # Allow your users to upload .mkv, .mov, .avi, .flv videos | ||
128 | allow_additional_extensions: true | ||
127 | threads: 1 | 129 | threads: 1 |
128 | resolutions: # Only created if the original video has a higher resolution, uses more storage! | 130 | resolutions: # Only created if the original video has a higher resolution, uses more storage! |
129 | 240p: false | 131 | 240p: false |
diff --git a/config/production.yaml.example b/config/production.yaml.example index 4c50a550b..770bb97da 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -137,6 +137,8 @@ user: | |||
137 | # Please, do not disable transcoding since many uploaded videos will not work | 137 | # Please, do not disable transcoding since many uploaded videos will not work |
138 | transcoding: | 138 | transcoding: |
139 | enabled: true | 139 | enabled: true |
140 | # Allow your users to upload .mkv, .mov, .avi, .flv videos | ||
141 | allow_additional_extensions: true | ||
140 | threads: 1 | 142 | threads: 1 |
141 | resolutions: # Only created if the original video has a higher resolution, uses more storage! | 143 | resolutions: # Only created if the original video has a higher resolution, uses more storage! |
142 | 240p: false | 144 | 240p: false |
diff --git a/config/test-2.yaml b/config/test-2.yaml index a80ec6e54..b6d319394 100644 --- a/config/test-2.yaml +++ b/config/test-2.yaml | |||
@@ -29,3 +29,4 @@ signup: | |||
29 | 29 | ||
30 | transcoding: | 30 | transcoding: |
31 | enabled: true | 31 | enabled: true |
32 | allow_additional_extensions: true | ||
diff --git a/config/test.yaml b/config/test.yaml index 51a77e2fd..6e9c56e0a 100644 --- a/config/test.yaml +++ b/config/test.yaml | |||
@@ -51,6 +51,7 @@ signup: | |||
51 | 51 | ||
52 | transcoding: | 52 | transcoding: |
53 | enabled: true | 53 | enabled: true |
54 | allow_additional_extensions: false | ||
54 | threads: 2 | 55 | threads: 2 |
55 | resolutions: | 56 | resolutions: |
56 | 240p: true | 57 | 240p: true |
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index d65e321e9..c75002aaf 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -172,7 +172,8 @@ async function updateCustomConfig (req: express.Request, res: express.Response, | |||
172 | 'instance.defaultClientRoute', | 172 | 'instance.defaultClientRoute', |
173 | 'instance.shortDescription', | 173 | 'instance.shortDescription', |
174 | 'cache.videoCaptions', | 174 | 'cache.videoCaptions', |
175 | 'signup.requiresEmailVerification' | 175 | 'signup.requiresEmailVerification', |
176 | 'transcoding.allowAdditionalExtensions' | ||
176 | ) | 177 | ) |
177 | toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota | 178 | toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota |
178 | toUpdateJSON.user['video_quota_daily'] = toUpdate.user.videoQuotaDaily | 179 | toUpdateJSON.user['video_quota_daily'] = toUpdate.user.videoQuotaDaily |
@@ -180,6 +181,7 @@ async function updateCustomConfig (req: express.Request, res: express.Response, | |||
180 | toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription | 181 | toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription |
181 | toUpdateJSON.instance['default_nsfw_policy'] = toUpdate.instance.defaultNSFWPolicy | 182 | toUpdateJSON.instance['default_nsfw_policy'] = toUpdate.instance.defaultNSFWPolicy |
182 | toUpdateJSON.signup['requires_email_verification'] = toUpdate.signup.requiresEmailVerification | 183 | toUpdateJSON.signup['requires_email_verification'] = toUpdate.signup.requiresEmailVerification |
184 | toUpdateJSON.transcoding['allow_additional_extensions'] = toUpdate.transcoding.allowAdditionalExtensions | ||
183 | 185 | ||
184 | await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 }) | 186 | await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 }) |
185 | 187 | ||
@@ -247,6 +249,7 @@ function customConfig (): CustomConfig { | |||
247 | }, | 249 | }, |
248 | transcoding: { | 250 | transcoding: { |
249 | enabled: CONFIG.TRANSCODING.ENABLED, | 251 | enabled: CONFIG.TRANSCODING.ENABLED, |
252 | allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS, | ||
250 | threads: CONFIG.TRANSCODING.THREADS, | 253 | threads: CONFIG.TRANSCODING.THREADS, |
251 | resolutions: { | 254 | resolutions: { |
252 | '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ], | 255 | '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ], |
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index d2456346b..f712b0f0b 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -2,7 +2,7 @@ import * as express from 'express' | |||
2 | import 'multer' | 2 | import 'multer' |
3 | import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared' | 3 | import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../../initializers' | 5 | import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers' |
6 | import { sendUpdateActor } from '../../../lib/activitypub/send' | 6 | import { sendUpdateActor } from '../../../lib/activitypub/send' |
7 | import { | 7 | import { |
8 | asyncMiddleware, | 8 | asyncMiddleware, |
@@ -42,7 +42,7 @@ import { AccountModel } from '../../../models/account/account' | |||
42 | 42 | ||
43 | const auditLogger = auditLoggerFactory('users-me') | 43 | const auditLogger = auditLoggerFactory('users-me') |
44 | 44 | ||
45 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) | 45 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) |
46 | 46 | ||
47 | const meRouter = express.Router() | 47 | const meRouter = express.Router() |
48 | 48 | ||
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index fd143a139..3d6a6af7f 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -22,7 +22,7 @@ import { createVideoChannel } from '../../lib/video-channel' | |||
22 | import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 22 | import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
23 | import { setAsyncActorKeys } from '../../lib/activitypub' | 23 | import { setAsyncActorKeys } from '../../lib/activitypub' |
24 | import { AccountModel } from '../../models/account/account' | 24 | import { AccountModel } from '../../models/account/account' |
25 | import { CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers' | 25 | import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../initializers' |
26 | import { logger } from '../../helpers/logger' | 26 | import { logger } from '../../helpers/logger' |
27 | import { VideoModel } from '../../models/video/video' | 27 | import { VideoModel } from '../../models/video/video' |
28 | import { updateAvatarValidator } from '../../middlewares/validators/avatar' | 28 | import { updateAvatarValidator } from '../../middlewares/validators/avatar' |
@@ -32,7 +32,7 @@ import { resetSequelizeInstance } from '../../helpers/database-utils' | |||
32 | import { UserModel } from '../../models/account/user' | 32 | import { UserModel } from '../../models/account/user' |
33 | 33 | ||
34 | const auditLogger = auditLoggerFactory('channels') | 34 | const auditLogger = auditLoggerFactory('channels') |
35 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) | 35 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) |
36 | 36 | ||
37 | const videoChannelRouter = express.Router() | 37 | const videoChannelRouter = express.Router() |
38 | 38 | ||
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts index 3ba918189..9b3661368 100644 --- a/server/controllers/api/videos/captions.ts +++ b/server/controllers/api/videos/captions.ts | |||
@@ -2,7 +2,7 @@ import * as express from 'express' | |||
2 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' | 2 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' |
3 | import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' | 3 | import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' |
4 | import { createReqFiles } from '../../../helpers/express-utils' | 4 | import { createReqFiles } from '../../../helpers/express-utils' |
5 | import { CONFIG, sequelizeTypescript, VIDEO_CAPTIONS_MIMETYPE_EXT } from '../../../initializers' | 5 | import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers' |
6 | import { getFormattedObjects } from '../../../helpers/utils' | 6 | import { getFormattedObjects } from '../../../helpers/utils' |
7 | import { VideoCaptionModel } from '../../../models/video/video-caption' | 7 | import { VideoCaptionModel } from '../../../models/video/video-caption' |
8 | import { VideoModel } from '../../../models/video/video' | 8 | import { VideoModel } from '../../../models/video/video' |
@@ -12,7 +12,7 @@ import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | |||
12 | 12 | ||
13 | const reqVideoCaptionAdd = createReqFiles( | 13 | const reqVideoCaptionAdd = createReqFiles( |
14 | [ 'captionfile' ], | 14 | [ 'captionfile' ], |
15 | VIDEO_CAPTIONS_MIMETYPE_EXT, | 15 | MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT, |
16 | { | 16 | { |
17 | captionfile: CONFIG.STORAGE.CAPTIONS_DIR | 17 | captionfile: CONFIG.STORAGE.CAPTIONS_DIR |
18 | } | 18 | } |
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index f27d648c7..099ab7b8d 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts | |||
@@ -3,14 +3,7 @@ import * as magnetUtil from 'magnet-uri' | |||
3 | import 'multer' | 3 | import 'multer' |
4 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' | 4 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' |
5 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' | 5 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' |
6 | import { | 6 | import { CONFIG, MIMETYPES, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers' |
7 | CONFIG, | ||
8 | IMAGE_MIMETYPE_EXT, | ||
9 | PREVIEWS_SIZE, | ||
10 | sequelizeTypescript, | ||
11 | THUMBNAILS_SIZE, | ||
12 | TORRENT_MIMETYPE_EXT | ||
13 | } from '../../../initializers' | ||
14 | import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' | 7 | import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' |
15 | import { createReqFiles } from '../../../helpers/express-utils' | 8 | import { createReqFiles } from '../../../helpers/express-utils' |
16 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
@@ -35,7 +28,7 @@ const videoImportsRouter = express.Router() | |||
35 | 28 | ||
36 | const reqVideoFileImport = createReqFiles( | 29 | const reqVideoFileImport = createReqFiles( |
37 | [ 'thumbnailfile', 'previewfile', 'torrentfile' ], | 30 | [ 'thumbnailfile', 'previewfile', 'torrentfile' ], |
38 | Object.assign({}, TORRENT_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT), | 31 | Object.assign({}, MIMETYPES.TORRENT.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT), |
39 | { | 32 | { |
40 | thumbnailfile: CONFIG.STORAGE.TMP_DIR, | 33 | thumbnailfile: CONFIG.STORAGE.TMP_DIR, |
41 | previewfile: CONFIG.STORAGE.TMP_DIR, | 34 | previewfile: CONFIG.STORAGE.TMP_DIR, |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 4e4697ef4..00a1302d1 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -7,15 +7,13 @@ import { logger } from '../../../helpers/logger' | |||
7 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 7 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' |
8 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' | 8 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' |
9 | import { | 9 | import { |
10 | CONFIG, | 10 | CONFIG, MIMETYPES, |
11 | IMAGE_MIMETYPE_EXT, | ||
12 | PREVIEWS_SIZE, | 11 | PREVIEWS_SIZE, |
13 | sequelizeTypescript, | 12 | sequelizeTypescript, |
14 | THUMBNAILS_SIZE, | 13 | THUMBNAILS_SIZE, |
15 | VIDEO_CATEGORIES, | 14 | VIDEO_CATEGORIES, |
16 | VIDEO_LANGUAGES, | 15 | VIDEO_LANGUAGES, |
17 | VIDEO_LICENCES, | 16 | VIDEO_LICENCES, |
18 | VIDEO_MIMETYPE_EXT, | ||
19 | VIDEO_PRIVACIES | 17 | VIDEO_PRIVACIES |
20 | } from '../../../initializers' | 18 | } from '../../../initializers' |
21 | import { | 19 | import { |
@@ -57,7 +55,7 @@ import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-u | |||
57 | import { videoCaptionsRouter } from './captions' | 55 | import { videoCaptionsRouter } from './captions' |
58 | import { videoImportsRouter } from './import' | 56 | import { videoImportsRouter } from './import' |
59 | import { resetSequelizeInstance } from '../../../helpers/database-utils' | 57 | import { resetSequelizeInstance } from '../../../helpers/database-utils' |
60 | import { rename } from 'fs-extra' | 58 | import { move } from 'fs-extra' |
61 | import { watchingRouter } from './watching' | 59 | import { watchingRouter } from './watching' |
62 | 60 | ||
63 | const auditLogger = auditLoggerFactory('videos') | 61 | const auditLogger = auditLoggerFactory('videos') |
@@ -65,7 +63,7 @@ const videosRouter = express.Router() | |||
65 | 63 | ||
66 | const reqVideoFileAdd = createReqFiles( | 64 | const reqVideoFileAdd = createReqFiles( |
67 | [ 'videofile', 'thumbnailfile', 'previewfile' ], | 65 | [ 'videofile', 'thumbnailfile', 'previewfile' ], |
68 | Object.assign({}, VIDEO_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT), | 66 | Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT), |
69 | { | 67 | { |
70 | videofile: CONFIG.STORAGE.TMP_DIR, | 68 | videofile: CONFIG.STORAGE.TMP_DIR, |
71 | thumbnailfile: CONFIG.STORAGE.TMP_DIR, | 69 | thumbnailfile: CONFIG.STORAGE.TMP_DIR, |
@@ -74,7 +72,7 @@ const reqVideoFileAdd = createReqFiles( | |||
74 | ) | 72 | ) |
75 | const reqVideoFileUpdate = createReqFiles( | 73 | const reqVideoFileUpdate = createReqFiles( |
76 | [ 'thumbnailfile', 'previewfile' ], | 74 | [ 'thumbnailfile', 'previewfile' ], |
77 | IMAGE_MIMETYPE_EXT, | 75 | MIMETYPES.IMAGE.MIMETYPE_EXT, |
78 | { | 76 | { |
79 | thumbnailfile: CONFIG.STORAGE.TMP_DIR, | 77 | thumbnailfile: CONFIG.STORAGE.TMP_DIR, |
80 | previewfile: CONFIG.STORAGE.TMP_DIR | 78 | previewfile: CONFIG.STORAGE.TMP_DIR |
@@ -208,7 +206,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
208 | // Move physical file | 206 | // Move physical file |
209 | const videoDir = CONFIG.STORAGE.VIDEOS_DIR | 207 | const videoDir = CONFIG.STORAGE.VIDEOS_DIR |
210 | const destination = join(videoDir, video.getVideoFilename(videoFile)) | 208 | const destination = join(videoDir, video.getVideoFilename(videoFile)) |
211 | await rename(videoPhysicalFile.path, destination) | 209 | await move(videoPhysicalFile.path, destination) |
212 | // This is important in case if there is another attempt in the retry process | 210 | // This is important in case if there is another attempt in the retry process |
213 | videoPhysicalFile.filename = video.getVideoFilename(videoFile) | 211 | videoPhysicalFile.filename = video.getVideoFilename(videoFile) |
214 | videoPhysicalFile.path = destination | 212 | videoPhysicalFile.path = destination |
diff --git a/server/helpers/custom-validators/video-captions.ts b/server/helpers/custom-validators/video-captions.ts index 177e9e86e..b33d90e18 100644 --- a/server/helpers/custom-validators/video-captions.ts +++ b/server/helpers/custom-validators/video-captions.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { CONSTRAINTS_FIELDS, VIDEO_CAPTIONS_MIMETYPE_EXT, VIDEO_LANGUAGES } from '../../initializers' | 1 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initializers' |
2 | import { exists, isFileValid } from './misc' | 2 | import { exists, isFileValid } from './misc' |
3 | import { Response } from 'express' | 3 | import { Response } from 'express' |
4 | import { VideoModel } from '../../models/video/video' | 4 | import { VideoModel } from '../../models/video/video' |
@@ -8,7 +8,7 @@ function isVideoCaptionLanguageValid (value: any) { | |||
8 | return exists(value) && VIDEO_LANGUAGES[ value ] !== undefined | 8 | return exists(value) && VIDEO_LANGUAGES[ value ] !== undefined |
9 | } | 9 | } |
10 | 10 | ||
11 | const videoCaptionTypes = Object.keys(VIDEO_CAPTIONS_MIMETYPE_EXT) | 11 | const videoCaptionTypes = Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT) |
12 | .concat([ 'application/octet-stream' ]) // MacOS sends application/octet-stream >< | 12 | .concat([ 'application/octet-stream' ]) // MacOS sends application/octet-stream >< |
13 | .map(m => `(${m})`) | 13 | .map(m => `(${m})`) |
14 | const videoCaptionTypesRegex = videoCaptionTypes.join('|') | 14 | const videoCaptionTypesRegex = videoCaptionTypes.join('|') |
diff --git a/server/helpers/custom-validators/video-imports.ts b/server/helpers/custom-validators/video-imports.ts index 4d6ab1fa4..ce9e9193c 100644 --- a/server/helpers/custom-validators/video-imports.ts +++ b/server/helpers/custom-validators/video-imports.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import 'express-validator' | 1 | import 'express-validator' |
2 | import 'multer' | 2 | import 'multer' |
3 | import * as validator from 'validator' | 3 | import * as validator from 'validator' |
4 | import { CONSTRAINTS_FIELDS, TORRENT_MIMETYPE_EXT, VIDEO_IMPORT_STATES } from '../../initializers' | 4 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_IMPORT_STATES } from '../../initializers' |
5 | import { exists, isFileValid } from './misc' | 5 | import { exists, isFileValid } from './misc' |
6 | import * as express from 'express' | 6 | import * as express from 'express' |
7 | import { VideoImportModel } from '../../models/video/video-import' | 7 | import { VideoImportModel } from '../../models/video/video-import' |
@@ -24,7 +24,7 @@ function isVideoImportStateValid (value: any) { | |||
24 | return exists(value) && VIDEO_IMPORT_STATES[ value ] !== undefined | 24 | return exists(value) && VIDEO_IMPORT_STATES[ value ] !== undefined |
25 | } | 25 | } |
26 | 26 | ||
27 | const videoTorrentImportTypes = Object.keys(TORRENT_MIMETYPE_EXT).map(m => `(${m})`) | 27 | const videoTorrentImportTypes = Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT).map(m => `(${m})`) |
28 | const videoTorrentImportRegex = videoTorrentImportTypes.join('|') | 28 | const videoTorrentImportRegex = videoTorrentImportTypes.join('|') |
29 | function isVideoImportTorrentFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { | 29 | function isVideoImportTorrentFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { |
30 | return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true) | 30 | return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true) |
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index a13b09ac8..e6f22e6c5 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -5,10 +5,9 @@ import 'multer' | |||
5 | import * as validator from 'validator' | 5 | import * as validator from 'validator' |
6 | import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared' | 6 | import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared' |
7 | import { | 7 | import { |
8 | CONSTRAINTS_FIELDS, | 8 | CONSTRAINTS_FIELDS, MIMETYPES, |
9 | VIDEO_CATEGORIES, | 9 | VIDEO_CATEGORIES, |
10 | VIDEO_LICENCES, | 10 | VIDEO_LICENCES, |
11 | VIDEO_MIMETYPE_EXT, | ||
12 | VIDEO_PRIVACIES, | 11 | VIDEO_PRIVACIES, |
13 | VIDEO_RATE_TYPES, | 12 | VIDEO_RATE_TYPES, |
14 | VIDEO_STATES | 13 | VIDEO_STATES |
@@ -83,10 +82,15 @@ function isVideoRatingTypeValid (value: string) { | |||
83 | return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1 | 82 | return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1 |
84 | } | 83 | } |
85 | 84 | ||
86 | const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`) | 85 | function isVideoFileExtnameValid (value: string) { |
87 | const videoFileTypesRegex = videoFileTypes.join('|') | 86 | return exists(value) && MIMETYPES.VIDEO.EXT_MIMETYPE[value] !== undefined |
87 | } | ||
88 | 88 | ||
89 | function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { | 89 | function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { |
90 | const videoFileTypesRegex = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT) | ||
91 | .map(m => `(${m})`) | ||
92 | .join('|') | ||
93 | |||
90 | return isFileValid(files, videoFileTypesRegex, 'videofile', null) | 94 | return isFileValid(files, videoFileTypesRegex, 'videofile', null) |
91 | } | 95 | } |
92 | 96 | ||
@@ -221,6 +225,7 @@ export { | |||
221 | isVideoStateValid, | 225 | isVideoStateValid, |
222 | isVideoViewsValid, | 226 | isVideoViewsValid, |
223 | isVideoRatingTypeValid, | 227 | isVideoRatingTypeValid, |
228 | isVideoFileExtnameValid, | ||
224 | isVideoDurationValid, | 229 | isVideoDurationValid, |
225 | isVideoTagValid, | 230 | isVideoTagValid, |
226 | isVideoPrivacyValid, | 231 | isVideoPrivacyValid, |
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index b51c7cfba..a7bc7eec8 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -19,7 +19,7 @@ function checkMissedConfig () { | |||
19 | 'signup.enabled', 'signup.limit', 'signup.requires_email_verification', | 19 | 'signup.enabled', 'signup.limit', 'signup.requires_email_verification', |
20 | 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', | 20 | 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', |
21 | 'redundancy.videos.strategies', 'redundancy.videos.check_interval', | 21 | 'redundancy.videos.strategies', 'redundancy.videos.check_interval', |
22 | 'transcoding.enabled', 'transcoding.threads', | 22 | 'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions', |
23 | 'import.videos.http.enabled', 'import.videos.torrent.enabled', | 23 | 'import.videos.http.enabled', 'import.videos.torrent.enabled', |
24 | 'trending.videos.interval_days', | 24 | 'trending.videos.interval_days', |
25 | 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', | 25 | 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index d4496bc34..ad61bee73 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -16,7 +16,7 @@ let config: IConfig = require('config') | |||
16 | 16 | ||
17 | // --------------------------------------------------------------------------- | 17 | // --------------------------------------------------------------------------- |
18 | 18 | ||
19 | const LAST_MIGRATION_VERSION = 290 | 19 | const LAST_MIGRATION_VERSION = 295 |
20 | 20 | ||
21 | // --------------------------------------------------------------------------- | 21 | // --------------------------------------------------------------------------- |
22 | 22 | ||
@@ -246,6 +246,7 @@ const CONFIG = { | |||
246 | }, | 246 | }, |
247 | TRANSCODING: { | 247 | TRANSCODING: { |
248 | get ENABLED () { return config.get<boolean>('transcoding.enabled') }, | 248 | get ENABLED () { return config.get<boolean>('transcoding.enabled') }, |
249 | get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') }, | ||
249 | get THREADS () { return config.get<number>('transcoding.threads') }, | 250 | get THREADS () { return config.get<number>('transcoding.threads') }, |
250 | RESOLUTIONS: { | 251 | RESOLUTIONS: { |
251 | get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') }, | 252 | get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') }, |
@@ -298,7 +299,7 @@ const CONFIG = { | |||
298 | 299 | ||
299 | // --------------------------------------------------------------------------- | 300 | // --------------------------------------------------------------------------- |
300 | 301 | ||
301 | const CONSTRAINTS_FIELDS = { | 302 | let CONSTRAINTS_FIELDS = { |
302 | USERS: { | 303 | USERS: { |
303 | NAME: { min: 1, max: 50 }, // Length | 304 | NAME: { min: 1, max: 50 }, // Length |
304 | DESCRIPTION: { min: 3, max: 1000 }, // Length | 305 | DESCRIPTION: { min: 3, max: 1000 }, // Length |
@@ -357,7 +358,7 @@ const CONSTRAINTS_FIELDS = { | |||
357 | max: 2 * 1024 * 1024 // 2MB | 358 | max: 2 * 1024 * 1024 // 2MB |
358 | } | 359 | } |
359 | }, | 360 | }, |
360 | EXTNAME: [ '.mp4', '.ogv', '.webm' ], | 361 | EXTNAME: buildVideosExtname(), |
361 | INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2 | 362 | INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2 |
362 | DURATION: { min: 0 }, // Number | 363 | DURATION: { min: 0 }, // Number |
363 | TAGS: { min: 0, max: 5 }, // Number of total tags | 364 | TAGS: { min: 0, max: 5 }, // Number of total tags |
@@ -480,27 +481,31 @@ const VIDEO_ABUSE_STATES = { | |||
480 | [VideoAbuseState.ACCEPTED]: 'Accepted' | 481 | [VideoAbuseState.ACCEPTED]: 'Accepted' |
481 | } | 482 | } |
482 | 483 | ||
483 | const VIDEO_MIMETYPE_EXT = { | 484 | const MIMETYPES = { |
484 | 'video/webm': '.webm', | 485 | VIDEO: { |
485 | 'video/ogg': '.ogv', | 486 | MIMETYPE_EXT: buildVideoMimetypeExt(), |
486 | 'video/mp4': '.mp4' | 487 | EXT_MIMETYPE: null as { [ id: string ]: string } |
487 | } | 488 | }, |
488 | const VIDEO_EXT_MIMETYPE = invert(VIDEO_MIMETYPE_EXT) | 489 | IMAGE: { |
489 | 490 | MIMETYPE_EXT: { | |
490 | const IMAGE_MIMETYPE_EXT = { | 491 | 'image/png': '.png', |
491 | 'image/png': '.png', | 492 | 'image/jpg': '.jpg', |
492 | 'image/jpg': '.jpg', | 493 | 'image/jpeg': '.jpg' |
493 | 'image/jpeg': '.jpg' | 494 | } |
494 | } | 495 | }, |
495 | 496 | VIDEO_CAPTIONS: { | |
496 | const VIDEO_CAPTIONS_MIMETYPE_EXT = { | 497 | MIMETYPE_EXT: { |
497 | 'text/vtt': '.vtt', | 498 | 'text/vtt': '.vtt', |
498 | 'application/x-subrip': '.srt' | 499 | 'application/x-subrip': '.srt' |
499 | } | 500 | } |
500 | 501 | }, | |
501 | const TORRENT_MIMETYPE_EXT = { | 502 | TORRENT: { |
502 | 'application/x-bittorrent': '.torrent' | 503 | MIMETYPE_EXT: { |
504 | 'application/x-bittorrent': '.torrent' | ||
505 | } | ||
506 | } | ||
503 | } | 507 | } |
508 | MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT) | ||
504 | 509 | ||
505 | // --------------------------------------------------------------------------- | 510 | // --------------------------------------------------------------------------- |
506 | 511 | ||
@@ -526,7 +531,7 @@ const ACTIVITY_PUB = { | |||
526 | COLLECTION_ITEMS_PER_PAGE: 10, | 531 | COLLECTION_ITEMS_PER_PAGE: 10, |
527 | FETCH_PAGE_LIMIT: 100, | 532 | FETCH_PAGE_LIMIT: 100, |
528 | URL_MIME_TYPES: { | 533 | URL_MIME_TYPES: { |
529 | VIDEO: Object.keys(VIDEO_MIMETYPE_EXT), | 534 | VIDEO: Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT), |
530 | TORRENT: [ 'application/x-bittorrent' ], | 535 | TORRENT: [ 'application/x-bittorrent' ], |
531 | MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ] | 536 | MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ] |
532 | }, | 537 | }, |
@@ -685,13 +690,12 @@ if (isTestInstance() === true) { | |||
685 | ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms' | 690 | ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms' |
686 | } | 691 | } |
687 | 692 | ||
688 | updateWebserverConfig() | 693 | updateWebserverUrls() |
689 | 694 | ||
690 | // --------------------------------------------------------------------------- | 695 | // --------------------------------------------------------------------------- |
691 | 696 | ||
692 | export { | 697 | export { |
693 | API_VERSION, | 698 | API_VERSION, |
694 | VIDEO_CAPTIONS_MIMETYPE_EXT, | ||
695 | AVATARS_SIZE, | 699 | AVATARS_SIZE, |
696 | ACCEPT_HEADERS, | 700 | ACCEPT_HEADERS, |
697 | BCRYPT_SALT_SIZE, | 701 | BCRYPT_SALT_SIZE, |
@@ -719,7 +723,6 @@ export { | |||
719 | FEEDS, | 723 | FEEDS, |
720 | JOB_TTL, | 724 | JOB_TTL, |
721 | NSFW_POLICY_TYPES, | 725 | NSFW_POLICY_TYPES, |
722 | TORRENT_MIMETYPE_EXT, | ||
723 | STATIC_MAX_AGE, | 726 | STATIC_MAX_AGE, |
724 | STATIC_PATHS, | 727 | STATIC_PATHS, |
725 | VIDEO_IMPORT_TIMEOUT, | 728 | VIDEO_IMPORT_TIMEOUT, |
@@ -732,7 +735,6 @@ export { | |||
732 | VIDEO_LICENCES, | 735 | VIDEO_LICENCES, |
733 | VIDEO_STATES, | 736 | VIDEO_STATES, |
734 | VIDEO_RATE_TYPES, | 737 | VIDEO_RATE_TYPES, |
735 | VIDEO_MIMETYPE_EXT, | ||
736 | VIDEO_TRANSCODING_FPS, | 738 | VIDEO_TRANSCODING_FPS, |
737 | FFMPEG_NICE, | 739 | FFMPEG_NICE, |
738 | VIDEO_ABUSE_STATES, | 740 | VIDEO_ABUSE_STATES, |
@@ -740,13 +742,12 @@ export { | |||
740 | USER_PASSWORD_RESET_LIFETIME, | 742 | USER_PASSWORD_RESET_LIFETIME, |
741 | MEMOIZE_TTL, | 743 | MEMOIZE_TTL, |
742 | USER_EMAIL_VERIFY_LIFETIME, | 744 | USER_EMAIL_VERIFY_LIFETIME, |
743 | IMAGE_MIMETYPE_EXT, | ||
744 | OVERVIEWS, | 745 | OVERVIEWS, |
745 | SCHEDULER_INTERVALS_MS, | 746 | SCHEDULER_INTERVALS_MS, |
746 | REPEAT_JOBS, | 747 | REPEAT_JOBS, |
747 | STATIC_DOWNLOAD_PATHS, | 748 | STATIC_DOWNLOAD_PATHS, |
748 | RATES_LIMIT, | 749 | RATES_LIMIT, |
749 | VIDEO_EXT_MIMETYPE, | 750 | MIMETYPES, |
750 | CRAWL_REQUEST_CONCURRENCY, | 751 | CRAWL_REQUEST_CONCURRENCY, |
751 | JOB_COMPLETED_LIFETIME, | 752 | JOB_COMPLETED_LIFETIME, |
752 | HTTP_SIGNATURE, | 753 | HTTP_SIGNATURE, |
@@ -768,11 +769,43 @@ function getLocalConfigFilePath () { | |||
768 | return join(dirname(configSources[ 0 ].name), filename + '.json') | 769 | return join(dirname(configSources[ 0 ].name), filename + '.json') |
769 | } | 770 | } |
770 | 771 | ||
771 | function updateWebserverConfig () { | 772 | function buildVideoMimetypeExt () { |
773 | const data = { | ||
774 | 'video/webm': '.webm', | ||
775 | 'video/ogg': '.ogv', | ||
776 | 'video/mp4': '.mp4' | ||
777 | } | ||
778 | |||
779 | if (CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS) { | ||
780 | Object.assign(data, { | ||
781 | 'video/quicktime': '.mov', | ||
782 | 'video/x-msvideo': '.avi', | ||
783 | 'video/x-flv': '.flv', | ||
784 | 'video/x-matroska': '.mkv' | ||
785 | }) | ||
786 | } | ||
787 | |||
788 | return data | ||
789 | } | ||
790 | |||
791 | function updateWebserverUrls () { | ||
772 | CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT) | 792 | CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT) |
773 | CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP) | 793 | CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP) |
774 | } | 794 | } |
775 | 795 | ||
796 | function updateWebserverConfig () { | ||
797 | CONSTRAINTS_FIELDS.VIDEOS.EXTNAME = buildVideosExtname() | ||
798 | |||
799 | MIMETYPES.VIDEO.MIMETYPE_EXT = buildVideoMimetypeExt() | ||
800 | MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT) | ||
801 | } | ||
802 | |||
803 | function buildVideosExtname () { | ||
804 | return CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS | ||
805 | ? [ '.mp4', '.ogv', '.webm', '.mkv', '.mov', '.avi', '.flv' ] | ||
806 | : [ '.mp4', '.ogv', '.webm' ] | ||
807 | } | ||
808 | |||
776 | function buildVideosRedundancy (objs: any[]): VideosRedundancy[] { | 809 | function buildVideosRedundancy (objs: any[]): VideosRedundancy[] { |
777 | if (!objs) return [] | 810 | if (!objs) return [] |
778 | 811 | ||
@@ -854,4 +887,5 @@ export function reloadConfig () { | |||
854 | config = require('config') | 887 | config = require('config') |
855 | 888 | ||
856 | updateWebserverConfig() | 889 | updateWebserverConfig() |
890 | updateWebserverUrls() | ||
857 | } | 891 | } |
diff --git a/server/initializers/migrations/0295-video-file-extname.ts b/server/initializers/migrations/0295-video-file-extname.ts new file mode 100644 index 000000000..dbf249f66 --- /dev/null +++ b/server/initializers/migrations/0295-video-file-extname.ts | |||
@@ -0,0 +1,49 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | await utils.queryInterface.renameColumn('videoFile', 'extname', 'extname_old') | ||
11 | } | ||
12 | |||
13 | { | ||
14 | const data = { | ||
15 | type: Sequelize.STRING, | ||
16 | defaultValue: null, | ||
17 | allowNull: true | ||
18 | } | ||
19 | |||
20 | await utils.queryInterface.addColumn('videoFile', 'extname', data) | ||
21 | } | ||
22 | |||
23 | { | ||
24 | const query = 'UPDATE "videoFile" SET "extname" = "extname_old"::text' | ||
25 | await utils.sequelize.query(query) | ||
26 | } | ||
27 | |||
28 | { | ||
29 | const data = { | ||
30 | type: Sequelize.STRING, | ||
31 | defaultValue: null, | ||
32 | allowNull: false | ||
33 | } | ||
34 | await utils.queryInterface.changeColumn('videoFile', 'extname', data) | ||
35 | } | ||
36 | |||
37 | { | ||
38 | await utils.queryInterface.removeColumn('videoFile', 'extname_old') | ||
39 | } | ||
40 | } | ||
41 | |||
42 | function down (options) { | ||
43 | throw new Error('Not implemented.') | ||
44 | } | ||
45 | |||
46 | export { | ||
47 | up, | ||
48 | down | ||
49 | } | ||
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index bbe48833d..f7bf7c65a 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { join } from 'path' | ||
3 | import { Transaction } from 'sequelize' | 2 | import { Transaction } from 'sequelize' |
4 | import * as url from 'url' | 3 | import * as url from 'url' |
5 | import * as uuidv4 from 'uuid/v4' | 4 | import * as uuidv4 from 'uuid/v4' |
@@ -13,7 +12,7 @@ import { logger } from '../../helpers/logger' | |||
13 | import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' | 12 | import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' |
14 | import { doRequest, downloadImage } from '../../helpers/requests' | 13 | import { doRequest, downloadImage } from '../../helpers/requests' |
15 | import { getUrlFromWebfinger } from '../../helpers/webfinger' | 14 | import { getUrlFromWebfinger } from '../../helpers/webfinger' |
16 | import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers' | 15 | import { AVATARS_SIZE, CONFIG, MIMETYPES, sequelizeTypescript } from '../../initializers' |
17 | import { AccountModel } from '../../models/account/account' | 16 | import { AccountModel } from '../../models/account/account' |
18 | import { ActorModel } from '../../models/activitypub/actor' | 17 | import { ActorModel } from '../../models/activitypub/actor' |
19 | import { AvatarModel } from '../../models/avatar/avatar' | 18 | import { AvatarModel } from '../../models/avatar/avatar' |
@@ -172,10 +171,10 @@ async function fetchActorTotalItems (url: string) { | |||
172 | 171 | ||
173 | async function fetchAvatarIfExists (actorJSON: ActivityPubActor) { | 172 | async function fetchAvatarIfExists (actorJSON: ActivityPubActor) { |
174 | if ( | 173 | if ( |
175 | actorJSON.icon && actorJSON.icon.type === 'Image' && IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined && | 174 | actorJSON.icon && actorJSON.icon.type === 'Image' && MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined && |
176 | isActivityPubUrlValid(actorJSON.icon.url) | 175 | isActivityPubUrlValid(actorJSON.icon.url) |
177 | ) { | 176 | ) { |
178 | const extension = IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType] | 177 | const extension = MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] |
179 | 178 | ||
180 | const avatarName = uuidv4() + extension | 179 | const avatarName = uuidv4() + extension |
181 | await downloadImage(actorJSON.icon.url, CONFIG.STORAGE.AVATARS_DIR, avatarName, AVATARS_SIZE) | 180 | await downloadImage(actorJSON.icon.url, CONFIG.STORAGE.AVATARS_DIR, avatarName, AVATARS_SIZE) |
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 3d17e6846..379c2a0d7 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import * as sequelize from 'sequelize' | 2 | import * as sequelize from 'sequelize' |
3 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
4 | import { join } from 'path' | ||
5 | import * as request from 'request' | 4 | import * as request from 'request' |
6 | import { ActivityIconObject, ActivityUrlObject, ActivityVideoUrlObject, VideoState } from '../../../shared/index' | 5 | import { ActivityIconObject, ActivityUrlObject, ActivityVideoUrlObject, VideoState } from '../../../shared/index' |
7 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 6 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
@@ -11,7 +10,7 @@ import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos | |||
11 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' | 10 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' |
12 | import { logger } from '../../helpers/logger' | 11 | import { logger } from '../../helpers/logger' |
13 | import { doRequest, downloadImage } from '../../helpers/requests' | 12 | import { doRequest, downloadImage } from '../../helpers/requests' |
14 | import { ACTIVITY_PUB, CONFIG, REMOTE_SCHEME, sequelizeTypescript, THUMBNAILS_SIZE, VIDEO_MIMETYPE_EXT } from '../../initializers' | 13 | import { ACTIVITY_PUB, CONFIG, MIMETYPES, REMOTE_SCHEME, sequelizeTypescript, THUMBNAILS_SIZE } from '../../initializers' |
15 | import { ActorModel } from '../../models/activitypub/actor' | 14 | import { ActorModel } from '../../models/activitypub/actor' |
16 | import { TagModel } from '../../models/video/tag' | 15 | import { TagModel } from '../../models/video/tag' |
17 | import { VideoModel } from '../../models/video/video' | 16 | import { VideoModel } from '../../models/video/video' |
@@ -362,7 +361,7 @@ export { | |||
362 | // --------------------------------------------------------------------------- | 361 | // --------------------------------------------------------------------------- |
363 | 362 | ||
364 | function isActivityVideoUrlObject (url: ActivityUrlObject): url is ActivityVideoUrlObject { | 363 | function isActivityVideoUrlObject (url: ActivityUrlObject): url is ActivityVideoUrlObject { |
365 | const mimeTypes = Object.keys(VIDEO_MIMETYPE_EXT) | 364 | const mimeTypes = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT) |
366 | 365 | ||
367 | const urlMediaType = url.mediaType || url.mimeType | 366 | const urlMediaType = url.mediaType || url.mimeType |
368 | return mimeTypes.indexOf(urlMediaType) !== -1 && urlMediaType.startsWith('video/') | 367 | return mimeTypes.indexOf(urlMediaType) !== -1 && urlMediaType.startsWith('video/') |
@@ -490,7 +489,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid | |||
490 | 489 | ||
491 | const mediaType = fileUrl.mediaType || fileUrl.mimeType | 490 | const mediaType = fileUrl.mediaType || fileUrl.mimeType |
492 | const attribute = { | 491 | const attribute = { |
493 | extname: VIDEO_MIMETYPE_EXT[ mediaType ], | 492 | extname: MIMETYPES.VIDEO.MIMETYPE_EXT[ mediaType ], |
494 | infoHash: parsed.infoHash, | 493 | infoHash: parsed.infoHash, |
495 | resolution: fileUrl.height, | 494 | resolution: fileUrl.height, |
496 | size: fileUrl.size, | 495 | size: fileUrl.size, |
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index dd37dad22..8b6cd146a 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts | |||
@@ -15,7 +15,7 @@ import { | |||
15 | import { ActorModel } from '../activitypub/actor' | 15 | import { ActorModel } from '../activitypub/actor' |
16 | import { getVideoSort, throwIfNotValid } from '../utils' | 16 | import { getVideoSort, throwIfNotValid } from '../utils' |
17 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 17 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
18 | import { CONFIG, CONSTRAINTS_FIELDS, STATIC_PATHS, VIDEO_EXT_MIMETYPE } from '../../initializers' | 18 | import { CONFIG, CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers' |
19 | import { VideoFileModel } from '../video/video-file' | 19 | import { VideoFileModel } from '../video/video-file' |
20 | import { getServerActor } from '../../helpers/utils' | 20 | import { getServerActor } from '../../helpers/utils' |
21 | import { VideoModel } from '../video/video' | 21 | import { VideoModel } from '../video/video' |
@@ -415,8 +415,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
415 | expires: this.expiresOn.toISOString(), | 415 | expires: this.expiresOn.toISOString(), |
416 | url: { | 416 | url: { |
417 | type: 'Link', | 417 | type: 'Link', |
418 | mimeType: VIDEO_EXT_MIMETYPE[ this.VideoFile.extname ] as any, | 418 | mimeType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any, |
419 | mediaType: VIDEO_EXT_MIMETYPE[ this.VideoFile.extname ] as any, | 419 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any, |
420 | href: this.fileUrl, | 420 | href: this.fileUrl, |
421 | height: this.VideoFile.resolution, | 421 | height: this.VideoFile.resolution, |
422 | size: this.VideoFile.size, | 422 | size: this.VideoFile.size, |
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index adebdf0c7..3fd2d5a99 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts | |||
@@ -14,6 +14,7 @@ import { | |||
14 | UpdatedAt | 14 | UpdatedAt |
15 | } from 'sequelize-typescript' | 15 | } from 'sequelize-typescript' |
16 | import { | 16 | import { |
17 | isVideoFileExtnameValid, | ||
17 | isVideoFileInfoHashValid, | 18 | isVideoFileInfoHashValid, |
18 | isVideoFileResolutionValid, | 19 | isVideoFileResolutionValid, |
19 | isVideoFileSizeValid, | 20 | isVideoFileSizeValid, |
@@ -58,7 +59,8 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
58 | size: number | 59 | size: number |
59 | 60 | ||
60 | @AllowNull(false) | 61 | @AllowNull(false) |
61 | @Column(DataType.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME))) | 62 | @Is('VideoFileExtname', value => throwIfNotValid(value, isVideoFileExtnameValid, 'extname')) |
63 | @Column | ||
62 | extname: string | 64 | extname: string |
63 | 65 | ||
64 | @AllowNull(false) | 66 | @AllowNull(false) |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index e3f8d525b..de0747f22 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -2,7 +2,7 @@ import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' | |||
2 | import { VideoModel } from './video' | 2 | import { VideoModel } from './video' |
3 | import { VideoFileModel } from './video-file' | 3 | import { VideoFileModel } from './video-file' |
4 | import { ActivityUrlObject, VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 4 | import { ActivityUrlObject, VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
5 | import { CONFIG, THUMBNAILS_SIZE, VIDEO_EXT_MIMETYPE } from '../../initializers' | 5 | import { CONFIG, MIMETYPES, THUMBNAILS_SIZE } from '../../initializers' |
6 | import { VideoCaptionModel } from './video-caption' | 6 | import { VideoCaptionModel } from './video-caption' |
7 | import { | 7 | import { |
8 | getVideoCommentsActivityPubUrl, | 8 | getVideoCommentsActivityPubUrl, |
@@ -207,8 +207,8 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
207 | for (const file of video.VideoFiles) { | 207 | for (const file of video.VideoFiles) { |
208 | url.push({ | 208 | url.push({ |
209 | type: 'Link', | 209 | type: 'Link', |
210 | mimeType: VIDEO_EXT_MIMETYPE[ file.extname ] as any, | 210 | mimeType: MIMETYPES.VIDEO.EXT_MIMETYPE[ file.extname ] as any, |
211 | mediaType: VIDEO_EXT_MIMETYPE[ file.extname ] as any, | 211 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ file.extname ] as any, |
212 | href: video.getVideoFileUrl(file, baseUrlHttp), | 212 | href: video.getVideoFileUrl(file, baseUrlHttp), |
213 | height: file.resolution, | 213 | height: file.resolution, |
214 | size: file.size, | 214 | size: file.size, |
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index ffae380c1..b7bf41b58 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -54,6 +54,7 @@ describe('Test config API validators', function () { | |||
54 | }, | 54 | }, |
55 | transcoding: { | 55 | transcoding: { |
56 | enabled: true, | 56 | enabled: true, |
57 | allowAdditionalExtensions: true, | ||
57 | threads: 1, | 58 | threads: 1, |
58 | resolutions: { | 59 | resolutions: { |
59 | '240p': false, | 60 | '240p': false, |
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index d94eccf8e..f26b91435 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts | |||
@@ -320,10 +320,15 @@ describe('Test videos API validator', function () { | |||
320 | 320 | ||
321 | it('Should fail without an incorrect input file', async function () { | 321 | it('Should fail without an incorrect input file', async function () { |
322 | const fields = baseCorrectParams | 322 | const fields = baseCorrectParams |
323 | const attaches = { | 323 | let attaches = { |
324 | 'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short_fake.webm') | 324 | 'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short_fake.webm') |
325 | } | 325 | } |
326 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 326 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
327 | |||
328 | attaches = { | ||
329 | 'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short.mkv') | ||
330 | } | ||
331 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | ||
327 | }) | 332 | }) |
328 | 333 | ||
329 | it('Should fail with an incorrect thumbnail file', async function () { | 334 | it('Should fail with an incorrect thumbnail file', async function () { |
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index c5c360a17..4c163d47d 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -17,6 +17,7 @@ import { | |||
17 | setAccessTokensToServers, | 17 | setAccessTokensToServers, |
18 | updateCustomConfig | 18 | updateCustomConfig |
19 | } from '../../../../shared/utils' | 19 | } from '../../../../shared/utils' |
20 | import { ServerConfig } from '../../../../shared/models' | ||
20 | 21 | ||
21 | const expect = chai.expect | 22 | const expect = chai.expect |
22 | 23 | ||
@@ -43,6 +44,7 @@ function checkInitialConfig (data: CustomConfig) { | |||
43 | expect(data.user.videoQuota).to.equal(5242880) | 44 | expect(data.user.videoQuota).to.equal(5242880) |
44 | expect(data.user.videoQuotaDaily).to.equal(-1) | 45 | expect(data.user.videoQuotaDaily).to.equal(-1) |
45 | expect(data.transcoding.enabled).to.be.false | 46 | expect(data.transcoding.enabled).to.be.false |
47 | expect(data.transcoding.allowAdditionalExtensions).to.be.false | ||
46 | expect(data.transcoding.threads).to.equal(2) | 48 | expect(data.transcoding.threads).to.equal(2) |
47 | expect(data.transcoding.resolutions['240p']).to.be.true | 49 | expect(data.transcoding.resolutions['240p']).to.be.true |
48 | expect(data.transcoding.resolutions['360p']).to.be.true | 50 | expect(data.transcoding.resolutions['360p']).to.be.true |
@@ -74,6 +76,7 @@ function checkUpdatedConfig (data: CustomConfig) { | |||
74 | expect(data.user.videoQuotaDaily).to.equal(318742) | 76 | expect(data.user.videoQuotaDaily).to.equal(318742) |
75 | expect(data.transcoding.enabled).to.be.true | 77 | expect(data.transcoding.enabled).to.be.true |
76 | expect(data.transcoding.threads).to.equal(1) | 78 | expect(data.transcoding.threads).to.equal(1) |
79 | expect(data.transcoding.allowAdditionalExtensions).to.be.true | ||
77 | expect(data.transcoding.resolutions['240p']).to.be.false | 80 | expect(data.transcoding.resolutions['240p']).to.be.false |
78 | expect(data.transcoding.resolutions['360p']).to.be.true | 81 | expect(data.transcoding.resolutions['360p']).to.be.true |
79 | expect(data.transcoding.resolutions['480p']).to.be.true | 82 | expect(data.transcoding.resolutions['480p']).to.be.true |
@@ -96,7 +99,7 @@ describe('Test config', function () { | |||
96 | 99 | ||
97 | it('Should have a correct config on a server with registration enabled', async function () { | 100 | it('Should have a correct config on a server with registration enabled', async function () { |
98 | const res = await getConfig(server.url) | 101 | const res = await getConfig(server.url) |
99 | const data = res.body | 102 | const data: ServerConfig = res.body |
100 | 103 | ||
101 | expect(data.signup.allowed).to.be.true | 104 | expect(data.signup.allowed).to.be.true |
102 | }) | 105 | }) |
@@ -111,11 +114,21 @@ describe('Test config', function () { | |||
111 | ]) | 114 | ]) |
112 | 115 | ||
113 | const res = await getConfig(server.url) | 116 | const res = await getConfig(server.url) |
114 | const data = res.body | 117 | const data: ServerConfig = res.body |
115 | 118 | ||
116 | expect(data.signup.allowed).to.be.false | 119 | expect(data.signup.allowed).to.be.false |
117 | }) | 120 | }) |
118 | 121 | ||
122 | it('Should have the correct video allowed extensions', async function () { | ||
123 | const res = await getConfig(server.url) | ||
124 | const data: ServerConfig = res.body | ||
125 | |||
126 | expect(data.video.file.extensions).to.have.lengthOf(3) | ||
127 | expect(data.video.file.extensions).to.contain('.mp4') | ||
128 | expect(data.video.file.extensions).to.contain('.webm') | ||
129 | expect(data.video.file.extensions).to.contain('.ogv') | ||
130 | }) | ||
131 | |||
119 | it('Should get the customized configuration', async function () { | 132 | it('Should get the customized configuration', async function () { |
120 | const res = await getCustomConfig(server.url, server.accessToken) | 133 | const res = await getCustomConfig(server.url, server.accessToken) |
121 | const data = res.body as CustomConfig | 134 | const data = res.body as CustomConfig |
@@ -165,6 +178,7 @@ describe('Test config', function () { | |||
165 | }, | 178 | }, |
166 | transcoding: { | 179 | transcoding: { |
167 | enabled: true, | 180 | enabled: true, |
181 | allowAdditionalExtensions: true, | ||
168 | threads: 1, | 182 | threads: 1, |
169 | resolutions: { | 183 | resolutions: { |
170 | '240p': false, | 184 | '240p': false, |
@@ -193,6 +207,18 @@ describe('Test config', function () { | |||
193 | checkUpdatedConfig(data) | 207 | checkUpdatedConfig(data) |
194 | }) | 208 | }) |
195 | 209 | ||
210 | it('Should have the correct updated video allowed extensions', async function () { | ||
211 | const res = await getConfig(server.url) | ||
212 | const data: ServerConfig = res.body | ||
213 | |||
214 | expect(data.video.file.extensions).to.have.length.above(3) | ||
215 | expect(data.video.file.extensions).to.contain('.mp4') | ||
216 | expect(data.video.file.extensions).to.contain('.webm') | ||
217 | expect(data.video.file.extensions).to.contain('.ogv') | ||
218 | expect(data.video.file.extensions).to.contain('.flv') | ||
219 | expect(data.video.file.extensions).to.contain('.mkv') | ||
220 | }) | ||
221 | |||
196 | it('Should have the configuration updated after a restart', async function () { | 222 | it('Should have the configuration updated after a restart', async function () { |
197 | this.timeout(10000) | 223 | this.timeout(10000) |
198 | 224 | ||
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts index 68cf00194..eefd32ef8 100644 --- a/server/tests/api/videos/video-transcoder.ts +++ b/server/tests/api/videos/video-transcoder.ts | |||
@@ -20,9 +20,8 @@ import { | |||
20 | uploadVideo, | 20 | uploadVideo, |
21 | webtorrentAdd | 21 | webtorrentAdd |
22 | } from '../../../../shared/utils' | 22 | } from '../../../../shared/utils' |
23 | import { join } from 'path' | 23 | import { extname, join } from 'path' |
24 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 24 | import { waitJobs } from '../../../../shared/utils/server/jobs' |
25 | import { pathExists } from 'fs-extra' | ||
26 | import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants' | 25 | import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants' |
27 | 26 | ||
28 | const expect = chai.expect | 27 | const expect = chai.expect |
@@ -322,6 +321,34 @@ describe('Test video transcoding', function () { | |||
322 | } | 321 | } |
323 | }) | 322 | }) |
324 | 323 | ||
324 | it('Should accept and transcode additional extensions', async function () { | ||
325 | this.timeout(300000) | ||
326 | |||
327 | for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) { | ||
328 | const videoAttributes = { | ||
329 | name: fixture, | ||
330 | fixture | ||
331 | } | ||
332 | |||
333 | await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes) | ||
334 | |||
335 | await waitJobs(servers) | ||
336 | |||
337 | for (const server of servers) { | ||
338 | const res = await getVideosList(server.url) | ||
339 | |||
340 | const video = res.body.data.find(v => v.name === videoAttributes.name) | ||
341 | const res2 = await getVideo(server.url, video.id) | ||
342 | const videoDetails = res2.body | ||
343 | |||
344 | expect(videoDetails.files).to.have.lengthOf(4) | ||
345 | |||
346 | const magnetUri = videoDetails.files[ 0 ].magnetUri | ||
347 | expect(magnetUri).to.contain('.mp4') | ||
348 | } | ||
349 | } | ||
350 | }) | ||
351 | |||
325 | after(async function () { | 352 | after(async function () { |
326 | killallServers(servers) | 353 | killallServers(servers) |
327 | }) | 354 | }) |
diff --git a/server/tests/fixtures/video_short.avi b/server/tests/fixtures/video_short.avi new file mode 100644 index 000000000..88979cab2 --- /dev/null +++ b/server/tests/fixtures/video_short.avi | |||
Binary files differ | |||
diff --git a/server/tests/fixtures/video_short.mkv b/server/tests/fixtures/video_short.mkv new file mode 100644 index 000000000..a67f4f806 --- /dev/null +++ b/server/tests/fixtures/video_short.mkv | |||
Binary files differ | |||
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts index 3afd36fcd..028aafa1a 100644 --- a/shared/models/server/custom-config.model.ts +++ b/shared/models/server/custom-config.model.ts | |||
@@ -48,6 +48,7 @@ export interface CustomConfig { | |||
48 | 48 | ||
49 | transcoding: { | 49 | transcoding: { |
50 | enabled: boolean | 50 | enabled: boolean |
51 | allowAdditionalExtensions: boolean | ||
51 | threads: number | 52 | threads: number |
52 | resolutions: { | 53 | resolutions: { |
53 | '240p': boolean | 54 | '240p': boolean |
diff --git a/shared/utils/server/config.ts b/shared/utils/server/config.ts index 5b888b061..ff5288c82 100644 --- a/shared/utils/server/config.ts +++ b/shared/utils/server/config.ts | |||
@@ -86,6 +86,7 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) { | |||
86 | }, | 86 | }, |
87 | transcoding: { | 87 | transcoding: { |
88 | enabled: true, | 88 | enabled: true, |
89 | allowAdditionalExtensions: true, | ||
89 | threads: 1, | 90 | threads: 1, |
90 | resolutions: { | 91 | resolutions: { |
91 | '240p': false, | 92 | '240p': false, |