mirror of
https://github.com/immich-app/immich.git
synced 2026-07-08 05:17:54 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c953908d9 | |||
| 5278e64e34 | |||
| 924e6251fd |
@@ -89,7 +89,7 @@ Information on the current workers can be found [here](/administration/jobs-work
|
||||
|
||||
\*1: The values of `DB_USERNAME`, `DB_PASSWORD`, and `DB_DATABASE_NAME` are passed to the Postgres container as the variables `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` in `docker-compose.yml`.
|
||||
|
||||
\*2: If not provided, the appropriate extension to use is auto-detected at startup by introspecting the database. When multiple extensions are installed, the order of preference is VectorChord, pgvecto.rs, pgvector.
|
||||
\*2: If not provided, the appropriate extension to use is auto-detected at startup by inspecting the database. When multiple extensions are installed, the order of preference is VectorChord, pgvector.
|
||||
|
||||
\*3: Uses either [`postgresql.ssd.conf`](https://github.com/immich-app/base-images/blob/main/postgres/postgresql.ssd.conf) or [`postgresql.hdd.conf`](https://github.com/immich-app/base-images/blob/main/postgres/postgresql.hdd.conf) which mainly controls the Postgres `effective_io_concurrency` setting to allow for concurrenct IO on SSDs and sequential IO on HDDs.
|
||||
|
||||
|
||||
@@ -183,6 +183,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
||||
|
||||
Future<void> saveLocalEndpoint(String url) async {
|
||||
await _ref.read(settingsProvider).write(.networkLocalEndpoint, url);
|
||||
await _apiService.updateHeaders();
|
||||
}
|
||||
|
||||
String? getSavedWifiName() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:immich_mobile/domain/models/settings_key.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
||||
import 'package:immich_mobile/widgets/settings/networking_settings/endpoint_input.dart';
|
||||
|
||||
@@ -26,13 +27,16 @@ class ExternalNetworkPreference extends HookConsumerWidget {
|
||||
.map((e) => e.url)
|
||||
.toList();
|
||||
|
||||
ref.read(settingsProvider).write(SettingsKey.networkExternalEndpointList, urls);
|
||||
return ref.read(settingsProvider).write(SettingsKey.networkExternalEndpointList, urls);
|
||||
}
|
||||
|
||||
updateValidationStatus(String url, int index, AuxCheckStatus status) {
|
||||
updateValidationStatus(String url, int index, AuxCheckStatus status) async {
|
||||
entries.value[index] = entries.value[index].copyWith(url: url, status: status);
|
||||
|
||||
saveEndpointList();
|
||||
await saveEndpointList();
|
||||
if (status == AuxCheckStatus.valid) {
|
||||
await ref.read(apiServiceProvider).updateHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
handleReorder(int oldIndex, int newIndex) {
|
||||
|
||||
@@ -3,9 +3,10 @@ import { ExifDateTime, exiftool, WriteTags } from 'exiftool-vendored';
|
||||
import ffmpeg, { FfprobeData, FfprobeStream } from 'fluent-ffmpeg';
|
||||
import _ from 'lodash';
|
||||
import { Duration } from 'luxon';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { execFile as execFileCb } from 'node:child_process';
|
||||
import fs from 'node:fs/promises';
|
||||
import { Writable } from 'node:stream';
|
||||
import { promisify } from 'node:util';
|
||||
import sharp from 'sharp';
|
||||
import { ORIENTATION_TO_SHARP_ROTATION } from 'src/constants';
|
||||
import { Exif } from 'src/database';
|
||||
@@ -43,6 +44,8 @@ const probe = (input: string, options: string[]): Promise<FfprobeData> =>
|
||||
ffmpeg.ffprobe(input, options, (error, data) => (error ? reject(error) : resolve(data))),
|
||||
);
|
||||
|
||||
const execFile = promisify(execFileCb);
|
||||
|
||||
sharp.concurrency(0);
|
||||
sharp.cache({ files: 0 });
|
||||
|
||||
@@ -288,37 +291,33 @@ export class MediaRepository {
|
||||
* Needed for accurate segments, especially when remuxing, seeking and/or VFR is involved.
|
||||
* Scanning packets for keyframes in JS is much faster than -skip_frame nokey since it avoids decoding the video.
|
||||
*/
|
||||
probePackets(input: string, streamIndex: number): Promise<VideoPacketInfo | null> {
|
||||
const ffprobe = spawn(
|
||||
'ffprobe',
|
||||
[
|
||||
'-v',
|
||||
'error',
|
||||
'-select_streams',
|
||||
String(streamIndex),
|
||||
'-show_entries',
|
||||
'packet=pts,duration,flags',
|
||||
'-of',
|
||||
'csv=p=0',
|
||||
input,
|
||||
],
|
||||
{ stdio: ['ignore', 'pipe', 'pipe'] },
|
||||
);
|
||||
async probePackets(input: string, streamIndex: number): Promise<VideoPacketInfo | null> {
|
||||
const { stdout } = await execFile('ffprobe', [
|
||||
'-v',
|
||||
'error',
|
||||
'-select_streams',
|
||||
String(streamIndex),
|
||||
'-show_entries',
|
||||
'packet=pts,duration,flags',
|
||||
'-of',
|
||||
'csv=p=0',
|
||||
input,
|
||||
]);
|
||||
|
||||
let totalDuration = 0;
|
||||
const keyframePts: number[] = [];
|
||||
const keyframeAccDuration: number[] = [];
|
||||
const keyframeOwnDuration: number[] = [];
|
||||
const postDiscard: { pts: number; duration: number }[] = [];
|
||||
const parseLine = (line: string) => {
|
||||
for (const line of stdout.split('\n')) {
|
||||
if (!line) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
const [ptsStr, durationStr, flags] = line.split(',');
|
||||
const pts = Number.parseInt(ptsStr);
|
||||
const duration = Number.parseInt(durationStr);
|
||||
if (Number.isNaN(pts) || Number.isNaN(duration) || !flags) {
|
||||
return;
|
||||
if (Number.isNaN(pts) || Number.isNaN(duration)) {
|
||||
continue;
|
||||
}
|
||||
// Discarded packets don't contribute to packet count, but still contribute to video duration
|
||||
totalDuration += duration;
|
||||
@@ -333,43 +332,20 @@ export class MediaRepository {
|
||||
// Non-keyframes are accounted for in totalDuration.
|
||||
keyframeOwnDuration.push(duration);
|
||||
}
|
||||
}
|
||||
|
||||
if (postDiscard.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
totalDuration,
|
||||
packetCount: postDiscard.length,
|
||||
outputFrames: this.cfrOutputFrames(postDiscard, postDiscard.length / totalDuration),
|
||||
keyframePts,
|
||||
keyframeAccDuration,
|
||||
keyframeOwnDuration,
|
||||
};
|
||||
|
||||
let stderr = '';
|
||||
let remainder = '';
|
||||
ffprobe.stderr.setEncoding('utf8');
|
||||
ffprobe.stderr.on('data', (chunk: string) => (stderr += chunk));
|
||||
ffprobe.stdout.setEncoding('utf8');
|
||||
ffprobe.stdout.on('data', (chunk: string) => {
|
||||
const lines = chunk.split('\n');
|
||||
lines[0] = remainder + lines[0];
|
||||
remainder = lines.pop() as string;
|
||||
for (const line of lines) {
|
||||
parseLine(line);
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise<VideoPacketInfo | null>((resolve, reject) => {
|
||||
ffprobe.on('error', reject);
|
||||
ffprobe.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
return reject(new Error(`ffprobe exited with code ${code}: ${stderr.trim()}`));
|
||||
}
|
||||
parseLine(remainder);
|
||||
if (postDiscard.length === 0) {
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
resolve({
|
||||
totalDuration,
|
||||
packetCount: postDiscard.length,
|
||||
outputFrames: this.cfrOutputFrames(postDiscard, postDiscard.length / totalDuration),
|
||||
keyframePts,
|
||||
keyframeAccDuration,
|
||||
keyframeOwnDuration,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
transcode(input: string, output: string | Writable, options: TranscodeCommand): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user