mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Add force transcode option (#2126)
This commit is contained in:
@@ -9,6 +9,8 @@ input GenerateMetadataInput {
|
||||
markerImagePreviews: Boolean
|
||||
markerScreenshots: Boolean
|
||||
transcodes: Boolean
|
||||
"""Generate transcodes even if not required"""
|
||||
forceTranscodes: Boolean
|
||||
phashes: Boolean
|
||||
interactiveHeatmapsSpeeds: Boolean
|
||||
|
||||
|
||||
@@ -252,9 +252,11 @@ func (j *GenerateJob) queueSceneJobs(scene *models.Scene, queue chan<- Task, tot
|
||||
}
|
||||
|
||||
if utils.IsTrue(j.input.Transcodes) {
|
||||
forceTranscode := utils.IsTrue(j.input.ForceTranscodes)
|
||||
task := &GenerateTranscodeTask{
|
||||
Scene: *scene,
|
||||
Overwrite: j.overwrite,
|
||||
Force: forceTranscode,
|
||||
fileNamingAlgorithm: j.fileNamingAlgo,
|
||||
}
|
||||
if task.isTranscodeNeeded() {
|
||||
|
||||
@@ -15,6 +15,9 @@ type GenerateTranscodeTask struct {
|
||||
Scene models.Scene
|
||||
Overwrite bool
|
||||
fileNamingAlgorithm models.HashAlgorithm
|
||||
|
||||
// is true, generate even if video is browser-supported
|
||||
Force bool
|
||||
}
|
||||
|
||||
func (t *GenerateTranscodeTask) GetDescription() string {
|
||||
@@ -49,7 +52,7 @@ func (t *GenerateTranscodeTask) Start(ctc context.Context) {
|
||||
audioCodec = ffmpeg.AudioCodec(t.Scene.AudioCodec.String)
|
||||
}
|
||||
|
||||
if ffmpeg.IsStreamable(videoCodec, audioCodec, container) {
|
||||
if !t.Force && ffmpeg.IsStreamable(videoCodec, audioCodec, container) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -95,6 +98,14 @@ func (t *GenerateTranscodeTask) Start(ctc context.Context) {
|
||||
// used only when counting files to generate, doesn't affect the actual transcode generation
|
||||
// if container is missing from DB it is treated as non supported in order not to delay the user
|
||||
func (t *GenerateTranscodeTask) isTranscodeNeeded() bool {
|
||||
hasTranscode := HasTranscode(&t.Scene, t.fileNamingAlgorithm)
|
||||
if !t.Overwrite && hasTranscode {
|
||||
return false
|
||||
}
|
||||
|
||||
if t.Force {
|
||||
return true
|
||||
}
|
||||
|
||||
videoCodec := t.Scene.VideoCodec.String
|
||||
container := ""
|
||||
@@ -111,9 +122,5 @@ func (t *GenerateTranscodeTask) isTranscodeNeeded() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
hasTranscode := HasTranscode(&t.Scene, t.fileNamingAlgorithm)
|
||||
if !t.Overwrite && hasTranscode {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
### ✨ New Features
|
||||
* Added option to force generation of transcodes for selected scenes. ([#2126](https://github.com/stashapp/stash/pull/2126))
|
||||
* Added selective clean task. ([#2125](https://github.com/stashapp/stash/pull/2125))
|
||||
* Show heatmaps and median stroke speed for interactive scenes on the scenes page. ([#2096](https://github.com/stashapp/stash/pull/2096))
|
||||
* Save task options when scanning, generating and auto-tagging. ([#1949](https://github.com/stashapp/stash/pull/1949), [#2061](https://github.com/stashapp/stash/pull/2061))
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import { Form, Button } from "react-bootstrap";
|
||||
import {
|
||||
mutateMetadataGenerate,
|
||||
useConfigureDefaults,
|
||||
} from "src/core/StashService";
|
||||
import { Modal, Icon, OperationButton } from "src/components/Shared";
|
||||
import { mutateMetadataGenerate } from "src/core/StashService";
|
||||
import { Modal, Icon } from "src/components/Shared";
|
||||
import { useToast } from "src/hooks";
|
||||
import * as GQL from "src/core/generated-graphql";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
// import { DirectorySelectionDialog } from "../Settings/SettingsTasksPanel/DirectorySelectionDialog";
|
||||
import { Manual } from "../Help/Manual";
|
||||
import { withoutTypename } from "src/utils";
|
||||
import { GenerateOptions } from "../Settings/Tasks/GenerateOptions";
|
||||
@@ -25,7 +21,6 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
onClose,
|
||||
}) => {
|
||||
const { configuration } = React.useContext(ConfigurationContext);
|
||||
const [configureDefaults] = useConfigureDefaults();
|
||||
|
||||
function getDefaultOptions(): GQL.GenerateMetadataInput {
|
||||
return {
|
||||
@@ -45,10 +40,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
getDefaultOptions()
|
||||
);
|
||||
const [configRead, setConfigRead] = useState(false);
|
||||
const [paths /* , setPaths */] = useState<string[]>([]);
|
||||
const [showManual, setShowManual] = useState(false);
|
||||
// const [settingPaths, setSettingPaths] = useState(false);
|
||||
const [savingDefaults, setSavingDefaults] = useState(false);
|
||||
const [animation, setAnimation] = useState(true);
|
||||
|
||||
const intl = useIntl();
|
||||
@@ -111,16 +103,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
const message = paths.length ? (
|
||||
<div>
|
||||
<FormattedMessage id="config.tasks.generate.generating_from_paths" />:
|
||||
<ul>
|
||||
{paths.map((p) => (
|
||||
<li key={p}>{p}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
const message = (
|
||||
<span>
|
||||
<FormattedMessage
|
||||
id="config.tasks.generate.generating_scenes"
|
||||
@@ -140,27 +123,12 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
</span>
|
||||
);
|
||||
|
||||
// function onClick() {
|
||||
// setAnimation(false);
|
||||
// setSettingPaths(true);
|
||||
// }
|
||||
|
||||
return (
|
||||
<Form.Group className="dialog-selected-folders">
|
||||
<div>
|
||||
{message}
|
||||
{/* <div>
|
||||
<Button
|
||||
title={intl.formatMessage({ id: "actions.select_folders" })}
|
||||
onClick={() => onClick()}
|
||||
>
|
||||
<Icon icon="folder-open" />
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
<div>{message}</div>
|
||||
</Form.Group>
|
||||
);
|
||||
}, [selectedIds, intl, paths]);
|
||||
}, [selectedIds, intl]);
|
||||
|
||||
async function onGenerate() {
|
||||
try {
|
||||
@@ -181,58 +149,11 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
}
|
||||
}
|
||||
|
||||
function makeDefaultGenerateInput() {
|
||||
const ret = options;
|
||||
// const { paths: _paths, ...withoutSpecifics } = ret;
|
||||
const { overwrite: _overwrite, ...withoutSpecifics } = ret;
|
||||
return withoutSpecifics;
|
||||
}
|
||||
|
||||
function onShowManual() {
|
||||
setAnimation(false);
|
||||
setShowManual(true);
|
||||
}
|
||||
|
||||
async function setAsDefault() {
|
||||
try {
|
||||
setSavingDefaults(true);
|
||||
await configureDefaults({
|
||||
variables: {
|
||||
input: {
|
||||
generate: makeDefaultGenerateInput(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Toast.success({
|
||||
content: intl.formatMessage(
|
||||
{ id: "config.tasks.defaults_set" },
|
||||
{ action: intl.formatMessage({ id: "actions.generate" }) }
|
||||
),
|
||||
});
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
} finally {
|
||||
setSavingDefaults(false);
|
||||
}
|
||||
}
|
||||
|
||||
// if (settingPaths) {
|
||||
// return (
|
||||
// <DirectorySelectionDialog
|
||||
// animation={false}
|
||||
// allowEmpty
|
||||
// initialPaths={paths}
|
||||
// onClose={(p) => {
|
||||
// if (p) {
|
||||
// setPaths(p);
|
||||
// }
|
||||
// setSettingPaths(false);
|
||||
// }}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
||||
if (showManual) {
|
||||
return (
|
||||
<Manual
|
||||
@@ -259,12 +180,6 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
text: intl.formatMessage({ id: "actions.cancel" }),
|
||||
variant: "secondary",
|
||||
}}
|
||||
disabled={savingDefaults}
|
||||
footerButtons={
|
||||
<OperationButton variant="secondary" operation={setAsDefault}>
|
||||
<FormattedMessage id="actions.set_as_default" />
|
||||
</OperationButton>
|
||||
}
|
||||
leftFooterButtons={
|
||||
<Button
|
||||
title="Help"
|
||||
@@ -278,7 +193,11 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
|
||||
<Form>
|
||||
{selectionStatus}
|
||||
<SettingSection>
|
||||
<GenerateOptions options={options} setOptions={setOptions} />
|
||||
<GenerateOptions
|
||||
options={options}
|
||||
setOptions={setOptions}
|
||||
selection
|
||||
/>
|
||||
</SettingSection>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -8,11 +8,13 @@ import {
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
interface IGenerateOptions {
|
||||
selection?: boolean;
|
||||
options: GQL.GenerateMetadataInput;
|
||||
setOptions: (s: GQL.GenerateMetadataInput) => void;
|
||||
}
|
||||
|
||||
export const GenerateOptions: React.FC<IGenerateOptions> = ({
|
||||
selection,
|
||||
options,
|
||||
setOptions: setOptionsState,
|
||||
}) => {
|
||||
@@ -110,6 +112,18 @@ export const GenerateOptions: React.FC<IGenerateOptions> = ({
|
||||
tooltipID="dialogs.scene_gen.transcodes_tooltip"
|
||||
onChange={(v) => setOptions({ transcodes: v })}
|
||||
/>
|
||||
{selection ? (
|
||||
<BooleanSetting
|
||||
id="force-transcode"
|
||||
className="sub-setting"
|
||||
checked={options.forceTranscodes ?? false}
|
||||
disabled={!options.transcodes}
|
||||
headingID="dialogs.scene_gen.force_transcodes"
|
||||
tooltipID="dialogs.scene_gen.force_transcodes_tooltip"
|
||||
onChange={(v) => setOptions({ forceTranscodes: v })}
|
||||
/>
|
||||
) : undefined}
|
||||
|
||||
<BooleanSetting
|
||||
id="phash-task"
|
||||
checked={options.phashes ?? false}
|
||||
|
||||
@@ -585,6 +585,8 @@
|
||||
},
|
||||
"overwrite_filter_confirm": "Are you sure you want to overwrite existing saved query {entityName}?",
|
||||
"scene_gen": {
|
||||
"force_transcodes": "Force Transcode generation",
|
||||
"force_transcodes_tooltip": "By default, transcodes are only generated when the video file is not supported in the browser. When enabled, transcodes will be generated even when the video file appears to be supported in the browser.",
|
||||
"image_previews": "Animated Image Previews",
|
||||
"image_previews_tooltip": "Animated WebP previews, only required if Preview Type is set to Animated Image.",
|
||||
"interactive_heatmap_speed": "Generate heatmaps and speeds for interactive scenes",
|
||||
|
||||
Reference in New Issue
Block a user