mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat(server): transcoding improvements (#1370)
* feat: support isEdited flag for SettingSwitch * feat: add transcodeAll ffmpeg settings for extra transcoding control * refactor: tidy up and rename current video transcoding code + transcode everything * feat: better video transcoding with ffprobe analyses video files to see if they are already in the desired format allows admin to choose to transcode all videos regardless of the current format * fix: always serve encoded video if it exists * feat: change video codec option to a select box, limit options removed previous video codec config option as it's incompatible with new options removed mapping for encoder to codec as we now store the codec in the config * feat: add video conversion job for transcoding previously missed videos * chore: fix spelling of job messages to pluralise assets * chore: fix prettier/eslint warnings * feat: force switch targetAudioCodec default to aac to avoid iOS incompatibility * chore: lint issues after rebase
This commit is contained in:
parent
8eb82836b9
commit
4e0fe27de3
31 changed files with 274 additions and 63 deletions
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
if (data) {
|
||||
notificationController.show({
|
||||
message: `Thumbnail generation job started for ${data} asset`,
|
||||
message: `Thumbnail generation job started for ${data} assets`,
|
||||
type: NotificationType.Info
|
||||
});
|
||||
} else {
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
|
||||
if (data) {
|
||||
notificationController.show({
|
||||
message: `Extract EXIF job started for ${data} asset`,
|
||||
message: `Extract EXIF job started for ${data} assets`,
|
||||
type: NotificationType.Info
|
||||
});
|
||||
} else {
|
||||
|
|
@ -87,7 +87,7 @@
|
|||
|
||||
if (data) {
|
||||
notificationController.show({
|
||||
message: `Object detection job started for ${data} asset`,
|
||||
message: `Object detection job started for ${data} assets`,
|
||||
type: NotificationType.Info
|
||||
});
|
||||
} else {
|
||||
|
|
@ -101,6 +101,28 @@
|
|||
}
|
||||
};
|
||||
|
||||
const runVideoConversion = async () => {
|
||||
try {
|
||||
const { data } = await api.jobApi.sendJobCommand(JobId.VideoConversion, {
|
||||
command: JobCommand.Start
|
||||
});
|
||||
|
||||
if (data) {
|
||||
notificationController.show({
|
||||
message: `Video conversion job started for ${data} assets`,
|
||||
type: NotificationType.Info
|
||||
});
|
||||
} else {
|
||||
notificationController.show({
|
||||
message: `No videos without an encoded version found`,
|
||||
type: NotificationType.Info
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, `Error running video conversion job, check console for more detail`);
|
||||
}
|
||||
};
|
||||
|
||||
const runTemplateMigration = async () => {
|
||||
try {
|
||||
const { data } = await api.jobApi.sendJobCommand(JobId.StorageTemplateMigration, {
|
||||
|
|
@ -159,6 +181,17 @@
|
|||
Note that some assets may not have any objects detected, this is normal.
|
||||
</JobTile>
|
||||
|
||||
<JobTile
|
||||
title={'Video transcoding'}
|
||||
subtitle={'Run video transcoding process to transcode videos not in the desired format'}
|
||||
on:click={runVideoConversion}
|
||||
jobStatus={allJobsStatus?.isVideoConversionActive}
|
||||
waitingJobCount={allJobsStatus?.videoConversionQueueCount.waiting}
|
||||
activeJobCount={allJobsStatus?.videoConversionQueueCount.active}
|
||||
>
|
||||
Note that some videos won't require transcoding, this is normal.
|
||||
</JobTile>
|
||||
|
||||
<JobTile
|
||||
title={'Storage migration'}
|
||||
subtitle={''}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,13 @@
|
|||
import { api, SystemConfigFFmpegDto } from '@api';
|
||||
import SettingButtonsRow from '../setting-buttons-row.svelte';
|
||||
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
|
||||
import SettingSelect from '../setting-select.svelte';
|
||||
import SettingSwitch from '../setting-switch.svelte';
|
||||
import _ from 'lodash';
|
||||
export let ffmpegConfig: SystemConfigFFmpegDto; // this is the config that is being edited
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
export let ffmpegConfig: SystemConfigFFmpegDto; // this is the config that is being edited
|
||||
|
||||
let savedConfig: SystemConfigFFmpegDto;
|
||||
let defaultConfig: SystemConfigFFmpegDto;
|
||||
|
||||
|
|
@ -99,11 +102,10 @@
|
|||
isEdited={!(ffmpegConfig.targetAudioCodec == savedConfig.targetAudioCodec)}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
<SettingSelect
|
||||
label="VIDEO CODEC (-vcodec)"
|
||||
bind:value={ffmpegConfig.targetVideoCodec}
|
||||
required={true}
|
||||
options={['h264', 'hevc', 'vp9']}
|
||||
isEdited={!(ffmpegConfig.targetVideoCodec == savedConfig.targetVideoCodec)}
|
||||
/>
|
||||
|
||||
|
|
@ -114,6 +116,13 @@
|
|||
required={true}
|
||||
isEdited={!(ffmpegConfig.targetScaling == savedConfig.targetScaling)}
|
||||
/>
|
||||
|
||||
<SettingSwitch
|
||||
title="TRANSCODE ALL"
|
||||
subtitle="Transcode all files, even if they already match the specified format?"
|
||||
bind:checked={ffmpegConfig.transcodeAll}
|
||||
isEdited={!(ffmpegConfig.transcodeAll == savedConfig.transcodeAll)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-4">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
<script lang="ts">
|
||||
import { quintOut } from 'svelte/easing';
|
||||
import { fly } from 'svelte/transition';
|
||||
|
||||
export let value: string;
|
||||
export let options: string[];
|
||||
export let label = '';
|
||||
export let isEdited = false;
|
||||
|
||||
const handleChange = (e: Event) => {
|
||||
value = (e.target as HTMLInputElement).value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class={`flex place-items-center gap-1 h-[26px]`}>
|
||||
<label class={`immich-form-label text-sm`} for={label}>{label}</label>
|
||||
|
||||
{#if isEdited}
|
||||
<div
|
||||
transition:fly={{ x: 10, duration: 200, easing: quintOut }}
|
||||
class="bg-orange-100 px-2 rounded-full text-orange-900 text-[10px]"
|
||||
>
|
||||
Unsaved change
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<select
|
||||
class="immich-form-input w-full"
|
||||
name="presets"
|
||||
id="preset-select"
|
||||
bind:value
|
||||
on:change={handleChange}
|
||||
>
|
||||
{#each options as option}
|
||||
<option value={option}>{option}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
|
@ -1,15 +1,29 @@
|
|||
<script lang="ts">
|
||||
import { quintOut } from 'svelte/easing';
|
||||
import { fly } from 'svelte/transition';
|
||||
|
||||
export let title: string;
|
||||
export let subtitle = '';
|
||||
export let checked = false;
|
||||
export let disabled = false;
|
||||
export let isEdited = false;
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between place-items-center">
|
||||
<div>
|
||||
<h2 class="immich-form-label text-sm">
|
||||
{title}
|
||||
</h2>
|
||||
<div class="flex place-items-center gap-1 h-[26px]">
|
||||
<label class="immich-form-label text-sm" for={title}>
|
||||
{title}
|
||||
</label>
|
||||
{#if isEdited}
|
||||
<div
|
||||
transition:fly={{ x: 10, duration: 200, easing: quintOut }}
|
||||
class="bg-orange-100 px-2 rounded-full text-orange-900 text-[10px]"
|
||||
>
|
||||
Unsaved change
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<p class="text-sm dark:text-immich-dark-fg">{subtitle}</p>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue