Add force transcode option (#2126)

This commit is contained in:
WithoutPants
2021-12-20 11:45:36 +11:00
committed by GitHub
parent f830d9cf13
commit b0cf04865a
7 changed files with 43 additions and 96 deletions

View File

@@ -9,6 +9,8 @@ input GenerateMetadataInput {
markerImagePreviews: Boolean markerImagePreviews: Boolean
markerScreenshots: Boolean markerScreenshots: Boolean
transcodes: Boolean transcodes: Boolean
"""Generate transcodes even if not required"""
forceTranscodes: Boolean
phashes: Boolean phashes: Boolean
interactiveHeatmapsSpeeds: Boolean interactiveHeatmapsSpeeds: Boolean

View File

@@ -252,9 +252,11 @@ func (j *GenerateJob) queueSceneJobs(scene *models.Scene, queue chan<- Task, tot
} }
if utils.IsTrue(j.input.Transcodes) { if utils.IsTrue(j.input.Transcodes) {
forceTranscode := utils.IsTrue(j.input.ForceTranscodes)
task := &GenerateTranscodeTask{ task := &GenerateTranscodeTask{
Scene: *scene, Scene: *scene,
Overwrite: j.overwrite, Overwrite: j.overwrite,
Force: forceTranscode,
fileNamingAlgorithm: j.fileNamingAlgo, fileNamingAlgorithm: j.fileNamingAlgo,
} }
if task.isTranscodeNeeded() { if task.isTranscodeNeeded() {

View File

@@ -15,6 +15,9 @@ type GenerateTranscodeTask struct {
Scene models.Scene Scene models.Scene
Overwrite bool Overwrite bool
fileNamingAlgorithm models.HashAlgorithm fileNamingAlgorithm models.HashAlgorithm
// is true, generate even if video is browser-supported
Force bool
} }
func (t *GenerateTranscodeTask) GetDescription() string { func (t *GenerateTranscodeTask) GetDescription() string {
@@ -49,7 +52,7 @@ func (t *GenerateTranscodeTask) Start(ctc context.Context) {
audioCodec = ffmpeg.AudioCodec(t.Scene.AudioCodec.String) audioCodec = ffmpeg.AudioCodec(t.Scene.AudioCodec.String)
} }
if ffmpeg.IsStreamable(videoCodec, audioCodec, container) { if !t.Force && ffmpeg.IsStreamable(videoCodec, audioCodec, container) {
return 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 // 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 // if container is missing from DB it is treated as non supported in order not to delay the user
func (t *GenerateTranscodeTask) isTranscodeNeeded() bool { 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 videoCodec := t.Scene.VideoCodec.String
container := "" container := ""
@@ -111,9 +122,5 @@ func (t *GenerateTranscodeTask) isTranscodeNeeded() bool {
return false return false
} }
hasTranscode := HasTranscode(&t.Scene, t.fileNamingAlgorithm)
if !t.Overwrite && hasTranscode {
return false
}
return true return true
} }

View File

@@ -1,4 +1,5 @@
### ✨ New Features ### ✨ 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)) * 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)) * 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)) * 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))

View File

@@ -1,15 +1,11 @@
import React, { useState, useEffect, useMemo } from "react"; import React, { useState, useEffect, useMemo } from "react";
import { Form, Button } from "react-bootstrap"; import { Form, Button } from "react-bootstrap";
import { import { mutateMetadataGenerate } from "src/core/StashService";
mutateMetadataGenerate, import { Modal, Icon } from "src/components/Shared";
useConfigureDefaults,
} from "src/core/StashService";
import { Modal, Icon, OperationButton } from "src/components/Shared";
import { useToast } from "src/hooks"; import { useToast } from "src/hooks";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { ConfigurationContext } from "src/hooks/Config"; import { ConfigurationContext } from "src/hooks/Config";
// import { DirectorySelectionDialog } from "../Settings/SettingsTasksPanel/DirectorySelectionDialog";
import { Manual } from "../Help/Manual"; import { Manual } from "../Help/Manual";
import { withoutTypename } from "src/utils"; import { withoutTypename } from "src/utils";
import { GenerateOptions } from "../Settings/Tasks/GenerateOptions"; import { GenerateOptions } from "../Settings/Tasks/GenerateOptions";
@@ -25,7 +21,6 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
onClose, onClose,
}) => { }) => {
const { configuration } = React.useContext(ConfigurationContext); const { configuration } = React.useContext(ConfigurationContext);
const [configureDefaults] = useConfigureDefaults();
function getDefaultOptions(): GQL.GenerateMetadataInput { function getDefaultOptions(): GQL.GenerateMetadataInput {
return { return {
@@ -45,10 +40,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
getDefaultOptions() getDefaultOptions()
); );
const [configRead, setConfigRead] = useState(false); const [configRead, setConfigRead] = useState(false);
const [paths /* , setPaths */] = useState<string[]>([]);
const [showManual, setShowManual] = useState(false); const [showManual, setShowManual] = useState(false);
// const [settingPaths, setSettingPaths] = useState(false);
const [savingDefaults, setSavingDefaults] = useState(false);
const [animation, setAnimation] = useState(true); const [animation, setAnimation] = useState(true);
const intl = useIntl(); const intl = useIntl();
@@ -111,16 +103,7 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
</Form.Group> </Form.Group>
); );
} }
const message = paths.length ? ( const message = (
<div>
<FormattedMessage id="config.tasks.generate.generating_from_paths" />:
<ul>
{paths.map((p) => (
<li key={p}>{p}</li>
))}
</ul>
</div>
) : (
<span> <span>
<FormattedMessage <FormattedMessage
id="config.tasks.generate.generating_scenes" id="config.tasks.generate.generating_scenes"
@@ -140,27 +123,12 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
</span> </span>
); );
// function onClick() {
// setAnimation(false);
// setSettingPaths(true);
// }
return ( return (
<Form.Group className="dialog-selected-folders"> <Form.Group className="dialog-selected-folders">
<div> <div>{message}</div>
{message}
{/* <div>
<Button
title={intl.formatMessage({ id: "actions.select_folders" })}
onClick={() => onClick()}
>
<Icon icon="folder-open" />
</Button>
</div> */}
</div>
</Form.Group> </Form.Group>
); );
}, [selectedIds, intl, paths]); }, [selectedIds, intl]);
async function onGenerate() { async function onGenerate() {
try { 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() { function onShowManual() {
setAnimation(false); setAnimation(false);
setShowManual(true); 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) { if (showManual) {
return ( return (
<Manual <Manual
@@ -259,12 +180,6 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
text: intl.formatMessage({ id: "actions.cancel" }), text: intl.formatMessage({ id: "actions.cancel" }),
variant: "secondary", variant: "secondary",
}} }}
disabled={savingDefaults}
footerButtons={
<OperationButton variant="secondary" operation={setAsDefault}>
<FormattedMessage id="actions.set_as_default" />
</OperationButton>
}
leftFooterButtons={ leftFooterButtons={
<Button <Button
title="Help" title="Help"
@@ -278,7 +193,11 @@ export const GenerateDialog: React.FC<ISceneGenerateDialog> = ({
<Form> <Form>
{selectionStatus} {selectionStatus}
<SettingSection> <SettingSection>
<GenerateOptions options={options} setOptions={setOptions} /> <GenerateOptions
options={options}
setOptions={setOptions}
selection
/>
</SettingSection> </SettingSection>
</Form> </Form>
</Modal> </Modal>

View File

@@ -8,11 +8,13 @@ import {
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
interface IGenerateOptions { interface IGenerateOptions {
selection?: boolean;
options: GQL.GenerateMetadataInput; options: GQL.GenerateMetadataInput;
setOptions: (s: GQL.GenerateMetadataInput) => void; setOptions: (s: GQL.GenerateMetadataInput) => void;
} }
export const GenerateOptions: React.FC<IGenerateOptions> = ({ export const GenerateOptions: React.FC<IGenerateOptions> = ({
selection,
options, options,
setOptions: setOptionsState, setOptions: setOptionsState,
}) => { }) => {
@@ -110,6 +112,18 @@ export const GenerateOptions: React.FC<IGenerateOptions> = ({
tooltipID="dialogs.scene_gen.transcodes_tooltip" tooltipID="dialogs.scene_gen.transcodes_tooltip"
onChange={(v) => setOptions({ transcodes: v })} 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 <BooleanSetting
id="phash-task" id="phash-task"
checked={options.phashes ?? false} checked={options.phashes ?? false}

View File

@@ -585,6 +585,8 @@
}, },
"overwrite_filter_confirm": "Are you sure you want to overwrite existing saved query {entityName}?", "overwrite_filter_confirm": "Are you sure you want to overwrite existing saved query {entityName}?",
"scene_gen": { "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": "Animated Image Previews",
"image_previews_tooltip": "Animated WebP previews, only required if Preview Type is set to Animated Image.", "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", "interactive_heatmap_speed": "Generate heatmaps and speeds for interactive scenes",