mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 20:34:37 +03:00
File storage rewrite (#2676)
* Restructure data layer part 2 (#2599) * Refactor and separate image model * Refactor image query builder * Handle relationships in image query builder * Remove relationship management methods * Refactor gallery model/query builder * Add scenes to gallery model * Convert scene model * Refactor scene models * Remove unused methods * Add unit tests for gallery * Add image tests * Add scene tests * Convert unnecessary scene value pointers to values * Convert unnecessary pointer values to values * Refactor scene partial * Add scene partial tests * Refactor ImagePartial * Add image partial tests * Refactor gallery partial update * Add partial gallery update tests * Use zero/null package for null values * Add files and scan system * Add sqlite implementation for files/folders * Add unit tests for files/folders * Image refactors * Update image data layer * Refactor gallery model and creation * Refactor scene model * Refactor scenes * Don't set title from filename * Allow galleries to freely add/remove images * Add multiple scene file support to graphql and UI * Add multiple file support for images in graphql/UI * Add multiple file for galleries in graphql/UI * Remove use of some deprecated fields * Remove scene path usage * Remove gallery path usage * Remove path from image * Move funscript to video file * Refactor caption detection * Migrate existing data * Add post commit/rollback hook system * Lint. Comment out import/export tests * Add WithDatabase read only wrapper * Prepend tasks to list * Add 32 pre-migration * Add warnings in release and migration notes
This commit is contained in:
@@ -1,26 +1,22 @@
|
||||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { Accordion, Card } from "react-bootstrap";
|
||||
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
||||
import { TruncatedText } from "src/components/Shared";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { NavUtils, TextUtils, getStashboxBase } from "src/utils";
|
||||
import { TextField, URLField } from "src/utils/field";
|
||||
|
||||
interface ISceneFileInfoPanelProps {
|
||||
scene: GQL.SceneDataFragment;
|
||||
interface IFileInfoPanelProps {
|
||||
file: GQL.VideoFileDataFragment;
|
||||
}
|
||||
|
||||
export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
|
||||
props: ISceneFileInfoPanelProps
|
||||
const FileInfoPanel: React.FC<IFileInfoPanelProps> = (
|
||||
props: IFileInfoPanelProps
|
||||
) => {
|
||||
const intl = useIntl();
|
||||
|
||||
function renderFileSize() {
|
||||
if (props.scene.file.size === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { size, unit } = TextUtils.fileSize(
|
||||
Number.parseInt(props.scene.file.size ?? "0", 10)
|
||||
);
|
||||
const { size, unit } = TextUtils.fileSize(props.file.size);
|
||||
|
||||
return (
|
||||
<TextField id="filesize">
|
||||
@@ -38,6 +34,78 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
|
||||
);
|
||||
}
|
||||
|
||||
// TODO - generalise fingerprints
|
||||
const oshash = props.file.fingerprints.find((f) => f.type === "oshash");
|
||||
const phash = props.file.fingerprints.find((f) => f.type === "phash");
|
||||
const checksum = props.file.fingerprints.find((f) => f.type === "md5");
|
||||
|
||||
return (
|
||||
<dl className="container scene-file-info details-list">
|
||||
<TextField id="media_info.hash" value={oshash?.value} truncate />
|
||||
<TextField id="media_info.checksum" value={checksum?.value} truncate />
|
||||
<URLField
|
||||
id="media_info.phash"
|
||||
abbr="Perceptual hash"
|
||||
value={phash?.value}
|
||||
url={NavUtils.makeScenesPHashMatchUrl(phash?.value)}
|
||||
target="_self"
|
||||
truncate
|
||||
trusted
|
||||
/>
|
||||
<URLField
|
||||
id="path"
|
||||
url={`file://${props.file.path}`}
|
||||
value={`file://${props.file.path}`}
|
||||
truncate
|
||||
/>
|
||||
{renderFileSize()}
|
||||
<TextField
|
||||
id="duration"
|
||||
value={TextUtils.secondsToTimestamp(props.file.duration ?? 0)}
|
||||
truncate
|
||||
/>
|
||||
<TextField
|
||||
id="dimensions"
|
||||
value={`${props.file.width} x ${props.file.height}`}
|
||||
truncate
|
||||
/>
|
||||
<TextField id="framerate">
|
||||
<FormattedMessage
|
||||
id="frames_per_second"
|
||||
values={{ value: intl.formatNumber(props.file.frame_rate ?? 0) }}
|
||||
/>
|
||||
</TextField>
|
||||
<TextField id="bitrate">
|
||||
<FormattedMessage
|
||||
id="megabits_per_second"
|
||||
values={{
|
||||
value: intl.formatNumber((props.file.bit_rate ?? 0) / 1000000, {
|
||||
maximumFractionDigits: 2,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</TextField>
|
||||
<TextField
|
||||
id="media_info.video_codec"
|
||||
value={props.file.video_codec ?? ""}
|
||||
truncate
|
||||
/>
|
||||
<TextField
|
||||
id="media_info.audio_codec"
|
||||
value={props.file.audio_codec ?? ""}
|
||||
truncate
|
||||
/>
|
||||
</dl>
|
||||
);
|
||||
};
|
||||
|
||||
interface ISceneFileInfoPanelProps {
|
||||
scene: GQL.SceneDataFragment;
|
||||
}
|
||||
|
||||
export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
|
||||
props: ISceneFileInfoPanelProps
|
||||
) => {
|
||||
function renderStashIDs() {
|
||||
if (!props.scene.stash_ids.length) {
|
||||
return;
|
||||
@@ -96,83 +164,55 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
|
||||
}
|
||||
}
|
||||
|
||||
const filesPanel = useMemo(() => {
|
||||
if (props.scene.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.scene.files.length === 1) {
|
||||
return <FileInfoPanel file={props.scene.files[0]} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion defaultActiveKey="0">
|
||||
{props.scene.files.map((file, index) => (
|
||||
<Card key={index} className="scene-file-card">
|
||||
<Accordion.Toggle as={Card.Header} eventKey={index.toString()}>
|
||||
<TruncatedText text={TextUtils.fileNameFromPath(file.path)} />
|
||||
</Accordion.Toggle>
|
||||
<Accordion.Collapse eventKey={index.toString()}>
|
||||
<Card.Body>
|
||||
<FileInfoPanel file={file} />
|
||||
</Card.Body>
|
||||
</Accordion.Collapse>
|
||||
</Card>
|
||||
))}
|
||||
</Accordion>
|
||||
);
|
||||
}, [props.scene]);
|
||||
|
||||
return (
|
||||
<dl className="container scene-file-info details-list">
|
||||
<TextField id="media_info.hash" value={props.scene.oshash} truncate />
|
||||
<TextField
|
||||
id="media_info.checksum"
|
||||
value={props.scene.checksum}
|
||||
truncate
|
||||
/>
|
||||
<URLField
|
||||
id="media_info.phash"
|
||||
abbr="Perceptual hash"
|
||||
value={props.scene.phash}
|
||||
url={NavUtils.makeScenesPHashMatchUrl(props.scene.phash)}
|
||||
target="_self"
|
||||
truncate
|
||||
trusted
|
||||
/>
|
||||
<URLField
|
||||
id="path"
|
||||
url={`file://${props.scene.path}`}
|
||||
value={`file://${props.scene.path}`}
|
||||
truncate
|
||||
/>
|
||||
<URLField
|
||||
id="media_info.stream"
|
||||
url={props.scene.paths.stream}
|
||||
value={props.scene.paths.stream}
|
||||
truncate
|
||||
/>
|
||||
{renderFunscript()}
|
||||
{renderInteractiveSpeed()}
|
||||
{renderFileSize()}
|
||||
<TextField
|
||||
id="duration"
|
||||
value={TextUtils.secondsToTimestamp(props.scene.file.duration ?? 0)}
|
||||
truncate
|
||||
/>
|
||||
<TextField
|
||||
id="dimensions"
|
||||
value={`${props.scene.file.width} x ${props.scene.file.height}`}
|
||||
truncate
|
||||
/>
|
||||
<TextField id="framerate">
|
||||
<FormattedMessage
|
||||
id="frames_per_second"
|
||||
values={{ value: intl.formatNumber(props.scene.file.framerate ?? 0) }}
|
||||
<>
|
||||
<dl className="container scene-file-info details-list">
|
||||
<URLField
|
||||
id="media_info.stream"
|
||||
url={props.scene.paths.stream}
|
||||
value={props.scene.paths.stream}
|
||||
truncate
|
||||
/>
|
||||
</TextField>
|
||||
<TextField id="bitrate">
|
||||
<FormattedMessage
|
||||
id="megabits_per_second"
|
||||
values={{
|
||||
value: intl.formatNumber(
|
||||
(props.scene.file.bitrate ?? 0) / 1000000,
|
||||
{ maximumFractionDigits: 2 }
|
||||
),
|
||||
}}
|
||||
{renderFunscript()}
|
||||
{renderInteractiveSpeed()}
|
||||
<URLField
|
||||
id="media_info.downloaded_from"
|
||||
url={props.scene.url}
|
||||
value={props.scene.url}
|
||||
truncate
|
||||
/>
|
||||
</TextField>
|
||||
<TextField
|
||||
id="media_info.video_codec"
|
||||
value={props.scene.file.video_codec}
|
||||
truncate
|
||||
/>
|
||||
<TextField
|
||||
id="media_info.audio_codec"
|
||||
value={props.scene.file.audio_codec}
|
||||
truncate
|
||||
/>
|
||||
<URLField
|
||||
id="media_info.downloaded_from"
|
||||
url={props.scene.url}
|
||||
value={props.scene.url}
|
||||
truncate
|
||||
/>
|
||||
{renderStashIDs()}
|
||||
</dl>
|
||||
{renderStashIDs()}
|
||||
</dl>
|
||||
|
||||
{filesPanel}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user