Compare commits

..

1 Commits

Author SHA1 Message Date
kermieisinthehouse
035fa13cc0 Readme: Update Translation information 2023-08-26 17:00:44 -04:00
4212 changed files with 1347931 additions and 193973 deletions

View File

@@ -17,7 +17,7 @@
# GraphQL generated output
pkg/models/generated_*.go
ui/v2.5/src/core/generated-graphql.ts
ui/v2.5/src/core/generated-*.tsx
####
# Jetbrains

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: stashapp
# patreon: # Replace with a single Patreon username
open_collective: stashapp
# ko_fi: # Replace with a single Ko-fi username
# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
# liberapay: StashApp
# issuehunt: # Replace with a single IssueHunt username
# otechie: # Replace with a single Otechie username
# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

40
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,40 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug Report] Short Form Subject (50 Chars or less)"
labels: bug report
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem please ensure that your screenshots are SFW or at least appropriately censored.
**Stash Version: (from Settings -> About):**
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,64 +0,0 @@
name: Bug Report
description: Create a report to help us fix the bug
labels: ["bug report"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: description
attributes:
label: Describe the bug
description: Provide a clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Detail the steps that would replicate this issue.
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behaviour
description: Provide clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: context
attributes:
label: Screenshots or additional context
description: Provide any additional context and SFW screenshots here to help us solve this issue.
validations:
required: false
- type: input
id: stashversion
attributes:
label: Stash version
description: This can be found in Settings > About.
placeholder: (e.g. v0.28.1)
validations:
required: true
- type: input
id: devicedetails
attributes:
label: Device details
description: |
If this is an issue that occurs when using the Stash interface, please provide details of the device/browser used which presents the reported issue.
placeholder: (e.g. Firefox 97 (64-bit) on Windows 11)
validations:
required: false
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output from Settings > Logs. This will be automatically formatted into code, so no need for backticks.
render: shell

View File

@@ -1,11 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Community forum
url: https://discourse.stashapp.cc
about: Start a discussion on the community forum.
- name: Community Discord
url: https://discord.gg/Y8MNsvQBvZ
about: Chat with the community on Discord.
- name: Documentation
url: https://docs.stashapp.cc
about: Check out documentation for help and information.

View File

@@ -0,0 +1,24 @@
---
name: Discussion / Request for Commentary [RFC]
about: This is for issues that will be discussed and won't necessarily result directly
in commits or pull requests.
title: "[RFC] Short Form Title"
labels: help wanted
assignees: ''
---
<!-- Update or delete the title if you need to delegate your title gore to something
# Title
*### Scope*
<!-- describe the scope of your topic and your goals ideally within a single paragraph or TL;DR kind of summary so its easier for people to determine if they can contribute at a glance. -->
## Long Form
<!-- Only required if your scope and titles can't cover everything. -->
## Examples
<!-- if you can show a picture or video examples post them here, please ensure that you respect people's time and attention and understand that people are volunteering their time, so concision is ideal and considerate. -->
## Reference Reading
<!-- if there is any reference reading or documentation, please refer to it here. -->

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature] Short Form Title (50 chars or less.)"
labels: feature request
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,44 +0,0 @@
name: Feature Request
description: Request a new feature or idea to be added to Stash
labels: ["feature request"]
body:
- type: textarea
id: description
attributes:
label: Describe the feature you'd like
description: Provide a clear description of the feature you'd like implemented
validations:
required: true
- type: textarea
id: benefits
attributes:
label: Describe the benefits this would bring to existing users
description: |
Explain the measurable benefits this feature would achieve for existing users.
The benefits should be described in terms of outcomes for users, not specific implementations.
validations:
required: true
- type: textarea
id: already_possible
attributes:
label: Is there an existing way to achieve this goal?
description: |
Yes/No. If Yes, describe how your proposed feature differs from or improves upon the current method
validations:
required: true
- type: checkboxes
id: confirm-search
attributes:
label: Have you searched for an existing open/closed issue?
description: |
To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/stashapp/stash/issues?q=is%3Aissue) for any existing issues that cover the core request or benefit of your proposal.
options:
- label: I have searched for existing issues and none cover the core request of my proposal
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.
validations:
required: false

View File

@@ -1,28 +0,0 @@
name: Compiler Build
on:
workflow_dispatch:
env:
COMPILER_IMAGE: ghcr.io/stashapp/compiler:13
jobs:
build-compiler:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
push: true
context: "{{defaultContext}}:docker/compiler"
tags: |
${{ env.COMPILER_IMAGE }}
ghcr.io/stashapp/compiler:latest
cache-from: type=gha,scope=all,mode=max
cache-to: type=gha,scope=all,mode=max

View File

@@ -2,10 +2,7 @@ name: Build
on:
push:
branches:
- develop
- master
- 'releases/**'
branches: [ develop, master ]
pull_request:
release:
types: [ published ]
@@ -15,163 +12,45 @@ concurrency:
cancel-in-progress: true
env:
COMPILER_IMAGE: ghcr.io/stashapp/compiler:13
COMPILER_IMAGE: stashapp/compiler:7
jobs:
# Job 1: Generate code and build UI
# Runs natively (no Docker) — go generate/gqlgen and node don't need cross-compilers.
# Produces artifacts (generated Go files + UI build) consumed by test and build jobs.
generate:
runs-on: ubuntu-24.04
build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v2
- name: Checkout
run: git fetch --prune --unshallow --tags
- name: Pull compiler image
run: docker pull $COMPILER_IMAGE
- name: Cache node modules
uses: actions/cache@v3
env:
cache-name: cache-node_modules
with:
fetch-depth: 0
fetch-tags: true
- name: Setup Go
uses: actions/setup-go@v6
# pnpm version is read from the packageManager field in package.json
# very broken (4.3, 4.4)
- name: Install pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
with:
package_json_file: ui/v2.5/package.json
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'pnpm'
cache-dependency-path: ui/v2.5/pnpm-lock.yaml
- name: Install UI dependencies
run: cd ui/v2.5 && pnpm install --frozen-lockfile
- name: Generate
run: make generate
path: ui/v2.5/node_modules
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/yarn.lock') }}
- name: Cache UI build
uses: actions/cache@v5
uses: actions/cache@v3
id: cache-ui
env:
cache-name: cache-ui
with:
path: ui/v2.5/build
key: ${{ runner.os }}-ui-build-${{ hashFiles('ui/v2.5/pnpm-lock.yaml', 'ui/v2.5/public/**', 'ui/v2.5/src/**', 'graphql/**/*.graphql') }}
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/yarn.lock', 'ui/v2.5/public/**', 'ui/v2.5/src/**', 'graphql/**/*.graphql') }}
- name: Validate UI
# skip UI validation for pull requests if UI is unchanged
if: ${{ github.event_name != 'pull_request' || steps.cache-ui.outputs.cache-hit != 'true' }}
run: make validate-ui
- name: Build UI
# skip UI build for pull requests if UI is unchanged (UI was cached)
if: ${{ github.event_name != 'pull_request' || steps.cache-ui.outputs.cache-hit != 'true' }}
run: make ui
# Bundle generated Go files + UI build for downstream jobs (test + build)
- name: Upload generated artifacts
uses: actions/upload-artifact@v7
with:
name: generated
retention-days: 1
path: |
internal/api/generated_exec.go
internal/api/generated_models.go
ui/v2.5/build/
ui/login/locales/
# Job 2: Integration tests
# Runs natively (no Docker) — only needs Go + GCC (for CGO/SQLite), both on ubuntu-22.04.
# Runs in parallel with the build matrix jobs.
test:
needs: generate
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
# Places generated Go files + UI build into the working tree so the build compiles
- name: Download generated artifacts
uses: actions/download-artifact@v8
with:
name: generated
- name: Test Backend
run: make it
# Job 3: Cross-compile for all platforms
# Each platform gets its own runner and Docker container (ghcr.io/stashapp/compiler:13).
# Each build-cc-* make target is self-contained (sets its own GOOS/GOARCH/CC),
# so running them in separate containers is functionally identical to one container.
# Runs in parallel with the test job.
build:
needs: generate
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
include:
- platform: windows
make-target: build-cc-windows
artifact-paths: |
dist/stash-win.exe
tag: win
- platform: macos
make-target: build-cc-macos
artifact-paths: |
dist/stash-macos
dist/Stash.app.zip
tag: osx
- platform: linux
make-target: build-cc-linux
artifact-paths: |
dist/stash-linux
tag: linux
- platform: linux-arm64v8
make-target: build-cc-linux-arm64v8
artifact-paths: |
dist/stash-linux-arm64v8
tag: arm
- platform: linux-arm32v7
make-target: build-cc-linux-arm32v7
artifact-paths: |
dist/stash-linux-arm32v7
tag: arm
- platform: linux-arm32v6
make-target: build-cc-linux-arm32v6
artifact-paths: |
dist/stash-linux-arm32v6
tag: arm
- platform: freebsd
make-target: build-cc-freebsd
artifact-paths: |
dist/stash-freebsd
tag: freebsd
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true
- name: Download generated artifacts
uses: actions/download-artifact@v8
with:
name: generated
- name: Cache Go build
uses: actions/cache@v5
- name: Cache go build
uses: actions/cache@v3
env:
# increment the number suffix to bump the cache
cache-name: cache-go-cache-1
with:
path: .go-cache
key: ${{ runner.os }}-go-cache-${{ matrix.platform }}-${{ hashFiles('go.mod', '**/go.sum') }}
# kept seperate to test timings
- name: pull compiler image
run: docker pull $COMPILER_IMAGE
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('go.mod', '**/go.sum') }}
- name: Start build container
env:
@@ -180,100 +59,76 @@ jobs:
mkdir -p .go-cache
docker run -d --name build --mount type=bind,source="$(pwd)",target=/stash,consistency=delegated --mount type=bind,source="$(pwd)/.go-cache",target=/root/.cache/go-build,consistency=delegated --env OFFICIAL_BUILD=${{ env.official-build }} -w /stash $COMPILER_IMAGE tail -f /dev/null
- name: Build (${{ matrix.platform }})
run: docker exec -t build /bin/bash -c "make ${{ matrix.make-target }}"
- name: Pre-install
run: docker exec -t build /bin/bash -c "make pre-ui"
- name: Generate
run: docker exec -t build /bin/bash -c "make generate"
- name: Validate UI
# skip UI validation for pull requests if UI is unchanged
if: ${{ github.event_name != 'pull_request' || steps.cache-ui.outputs.cache-hit != 'true' }}
run: docker exec -t build /bin/bash -c "make validate-ui"
# Static validation happens in the linter workflow in parallel to this workflow
# Run Dynamic validation here, to make sure we pass all the projects integration tests
- name: Test Backend
run: docker exec -t build /bin/bash -c "make it"
- name: Build UI
# skip UI build for pull requests if UI is unchanged (UI was cached)
# this means that the build version/time may be incorrect if the UI is
# not changed in a pull request
if: ${{ github.event_name != 'pull_request' || steps.cache-ui.outputs.cache-hit != 'true' }}
run: docker exec -t build /bin/bash -c "make ui"
- name: Compile for all supported platforms
run: |
docker exec -t build /bin/bash -c "make cross-compile-windows"
docker exec -t build /bin/bash -c "make cross-compile-macos-intel"
docker exec -t build /bin/bash -c "make cross-compile-macos-applesilicon"
docker exec -t build /bin/bash -c "make cross-compile-linux"
docker exec -t build /bin/bash -c "make cross-compile-linux-arm64v8"
docker exec -t build /bin/bash -c "make cross-compile-linux-arm32v7"
docker exec -t build /bin/bash -c "make cross-compile-linux-arm32v6"
- name: Cleanup build container
run: docker rm -f -v build
- name: Upload build artifact
uses: actions/upload-artifact@v7
with:
name: build-${{ matrix.platform }}
retention-days: 1
path: ${{ matrix.artifact-paths }}
# Job 4: Release
# Waits for both test and build to pass, then collects all platform artifacts
# into dist/ for checksums, GitHub releases, and multi-arch Docker push.
release:
needs: [test, build]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true
# Downloads all artifacts (generated + 7 platform builds) into artifacts/ subdirectories
- name: Download all build artifacts
uses: actions/download-artifact@v8
with:
path: artifacts
# Reassemble platform binaries from matrix job artifacts into a single dist/ directory
# make sure that artifacts have executable bit set
# upload-artifact@v4 strips the common path prefix (dist/), so files are at the artifact root
- name: Collect binaries
run: |
mkdir -p dist
cp artifacts/build-*/* dist/
chmod +x dist/*
- name: Zip UI
run: |
cd artifacts/generated/ui/v2.5/build && zip -r ../../../../../dist/stash-ui.zip .
- name: Generate checksums
run: |
git describe --tags --exclude latest_develop | tee CHECKSUMS_SHA1
sha1sum dist/Stash.app.zip dist/stash-* dist/stash-ui.zip | sed 's/dist\///g' | tee -a CHECKSUMS_SHA1
sha1sum dist/stash-* | sed 's/dist\///g' | tee -a CHECKSUMS_SHA1
echo "STASH_VERSION=$(git describe --tags --exclude latest_develop)" >> $GITHUB_ENV
echo "RELEASE_DATE=$(date +'%Y-%m-%d %H:%M:%S %Z')" >> $GITHUB_ENV
- name: Upload Windows binary
# only upload binaries for pull requests
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v2
with:
name: stash-win.exe
path: dist/stash-win.exe
- name: Upload macOS binary
- name: Upload OSX binary
# only upload binaries for pull requests
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v2
with:
name: stash-macos
path: dist/stash-macos
- name: Upload macOS bundle
# only upload binaries for pull requests
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
uses: actions/upload-artifact@v7
with:
name: Stash.app.zip
path: dist/Stash.app.zip
name: stash-macos-intel
path: dist/stash-macos-intel
- name: Upload Linux binary
# only upload binaries for pull requests
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v2
with:
name: stash-linux
path: dist/stash-linux
- name: Upload UI
# only upload for pull requests
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
uses: actions/upload-artifact@v7
with:
name: stash-ui.zip
path: dist/stash-ui.zip
- name: Update latest_develop tag
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
run: git tag -f latest_develop; git push -f --tags
run : git tag -f latest_develop; git push -f --tags
- name: Development Release
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
@@ -284,15 +139,13 @@ jobs:
automatic_release_tag: latest_develop
title: "${{ env.STASH_VERSION }}: Latest development build"
files: |
dist/Stash.app.zip
dist/stash-macos
dist/stash-macos-intel
dist/stash-macos-applesilicon
dist/stash-win.exe
dist/stash-linux
dist/stash-linux-arm64v8
dist/stash-linux-arm32v7
dist/stash-linux-arm32v6
dist/stash-freebsd
dist/stash-ui.zip
CHECKSUMS_SHA1
- name: Master release
@@ -304,15 +157,13 @@ jobs:
token: "${{ secrets.GITHUB_TOKEN }}"
allow_override: true
files: |
dist/Stash.app.zip
dist/stash-macos
dist/stash-macos-intel
dist/stash-macos-applesilicon
dist/stash-win.exe
dist/stash-linux
dist/stash-linux-arm64v8
dist/stash-linux-arm32v7
dist/stash-linux-arm32v6
dist/stash-freebsd
dist/stash-ui.zip
CHECKSUMS_SHA1
gzip: false
@@ -323,7 +174,7 @@ jobs:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
docker run --rm --privileged tonistiigi/binfmt
docker run --rm --privileged docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64
docker info
docker buildx create --name builder --use
docker buildx inspect --bootstrap
@@ -339,7 +190,7 @@ jobs:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
docker run --rm --privileged tonistiigi/binfmt
docker run --rm --privileged docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64
docker info
docker buildx create --name builder --use
docker buildx inspect --bootstrap

View File

@@ -6,23 +6,55 @@ on:
branches:
- master
- develop
- 'releases/**'
pull_request:
env:
COMPILER_IMAGE: stashapp/compiler:7
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
# no tags or depth needed for lint
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
- uses: actions/checkout@v2
- name: Checkout
run: git fetch --prune --unshallow --tags
- name: Pull compiler image
run: docker pull $COMPILER_IMAGE
- name: Start build container
run: |
mkdir -p .go-cache
docker run -d --name build --mount type=bind,source="$(pwd)",target=/stash,consistency=delegated --mount type=bind,source="$(pwd)/.go-cache",target=/root/.cache/go-build,consistency=delegated -w /stash $COMPILER_IMAGE tail -f /dev/null
# generate-backend runs natively (just go generate + touch-ui) — no Docker needed
- name: Generate Backend
run: make generate-backend
run: docker exec -t build /bin/bash -c "make generate-backend"
## WARN
## using v1, update in a later PR
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v6
uses: golangci/golangci-lint-action@v2
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.
args: --modules-download-mode=vendor --timeout=5m
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
# Optional: if set to true then the action will use pre-installed Go.
# skip-go-installation: true
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
skip-pkg-cache: true
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
skip-build-cache: true
- name: Cleanup build container
run: docker rm -f -v build

13
.gitignore vendored
View File

@@ -2,9 +2,6 @@
# Go
####
# Vendored dependencies
vendor
# Binaries for programs and plugins
*.exe
*.exe~
@@ -21,8 +18,10 @@ vendor
# GraphQL generated output
internal/api/generated_*.go
# Generated locale files
ui/login/locales/*
####
# Jetbrains
####
####
# Visual Studio
@@ -50,6 +49,9 @@ ui/login/locales/*
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Goland Junk
pkg/pkg
####
# Random
####
@@ -59,7 +61,6 @@ node_modules
*.db
/stash
/Stash.app
/phasher
dist
.DS_Store

View File

@@ -1,6 +1,7 @@
# options for analysis running
run:
timeout: 5m
modules-download-mode: vendor
linters:
disable-all: true
@@ -15,11 +16,11 @@ linters:
- unused
# Linters added by the stash project.
# - contextcheck
- copyloopvar
- dogsled
- errchkjson
- errorlint
# - exhaustive
- exportloopref
- gocritic
# - goerr113
- gofmt
@@ -48,6 +49,8 @@ linters-settings:
ignore-generated-header: true
severity: error
confidence: 0.8
error-code: 1
warning-code: 1
rules:
- name: blank-imports
disabled: true

View File

@@ -1,12 +1,14 @@
model:
package: graphql
filename: ./pkg/stashbox/graphql/generated_models.go
filename: ./pkg/scraper/stashbox/graphql/generated_models.go
client:
package: graphql
filename: ./pkg/stashbox/graphql/generated_client.go
filename: ./pkg/scraper/stashbox/graphql/generated_client.go
models:
Date:
model: github.com/99designs/gqlgen/graphql.String
SceneDraftInput:
model: github.com/stashapp/stash/pkg/scraper/stashbox/graphql.SceneDraftInput
endpoint:
# This points to stashdb.org currently, but can be directed at any stash-box
# instance. It is used for generation only.

3
.idea/go.iml generated
View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/certs" />
@@ -11,4 +10,4 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
</module>

View File

@@ -1,4 +0,0 @@
dir: ./pkg/models
name: ".*ReaderWriter"
outpkg: mocks
output: ./pkg/models/mocks

357
Makefile
View File

@@ -9,11 +9,9 @@ endif
ifdef IS_WIN_SHELL
RM := del /s /q
RMDIR := rmdir /s /q
NOOP := @@
else
RM := rm -f
RMDIR := rm -rf
NOOP := @:
endif
# set LDFLAGS environment variable to any extra ldflags required
@@ -38,7 +36,7 @@ GO_BUILD_FLAGS := $(GO_BUILD_FLAGS)
# set GO_BUILD_TAGS environment variable to any extra build tags required
GO_BUILD_TAGS := $(GO_BUILD_TAGS)
GO_BUILD_TAGS += sqlite_stat4 sqlite_math_functions
GO_BUILD_TAGS += sqlite_stat4
# set STASH_NOLEGACY environment variable or uncomment to disable legacy browser support
# STASH_NOLEGACY := true
@@ -48,49 +46,33 @@ GO_BUILD_TAGS += sqlite_stat4 sqlite_math_functions
export CGO_ENABLED := 1
# define COMPILER_IMAGE for cross-compilation docker container
ifndef COMPILER_IMAGE
COMPILER_IMAGE := ghcr.io/stashapp/compiler:latest
endif
.PHONY: release
release: pre-ui generate ui build-release
# targets to set various build flags
# use combinations on the make command-line to configure a build, e.g.:
# for a static-pie release build: `make flags-static-pie flags-release stash`
# for a static windows debug build: `make flags-static-windows stash`
# $(NOOP) prevents "nothing to be done" warnings
.PHONY: flags-release
flags-release:
$(NOOP)
flags-release:
$(eval LDFLAGS += -s -w)
$(eval GO_BUILD_FLAGS += -trimpath)
.PHONY: flags-pie
flags-pie:
$(NOOP)
$(eval GO_BUILD_FLAGS += -buildmode=pie)
.PHONY: flags-static
flags-static:
$(NOOP)
flags-static:
$(eval LDFLAGS += -extldflags=-static)
$(eval GO_BUILD_TAGS += sqlite_omit_load_extension osusergo netgo)
.PHONY: flags-static-pie
flags-static-pie:
$(NOOP)
flags-static-pie:
$(eval LDFLAGS += -extldflags=-static-pie)
$(eval GO_BUILD_FLAGS += -buildmode=pie)
$(eval GO_BUILD_TAGS += sqlite_omit_load_extension osusergo netgo)
# identical to flags-static-pie, but excluding netgo, which is not needed on windows
.PHONY: flags-static-windows
flags-static-windows:
$(NOOP)
$(eval LDFLAGS += -extldflags=-static-pie)
$(eval GO_BUILD_FLAGS += -buildmode=pie)
$(eval GO_BUILD_TAGS += sqlite_omit_load_extension osusergo)
@@ -123,149 +105,166 @@ build-flags: build-info
stash: build-flags
go build $(STASH_OUTPUT) $(BUILD_FLAGS) ./cmd/stash
.PHONY: stash-release
stash-release: flags-release
stash-release: flags-pie
stash-release: stash
.PHONY: stash-release-static
stash-release-static: flags-release
stash-release-static: flags-static-pie
stash-release-static: stash
.PHONY: stash-release-static-windows
stash-release-static-windows: flags-release
stash-release-static-windows: flags-static-windows
stash-release-static-windows: stash
.PHONY: phasher
phasher: build-flags
go build $(PHASHER_OUTPUT) $(BUILD_FLAGS) ./cmd/phasher
.PHONY: phasher-release
phasher-release: flags-release
phasher-release: flags-pie
phasher-release: phasher
.PHONY: phasher-release-static
phasher-release-static: flags-release
phasher-release-static: flags-static-pie
phasher-release-static: phasher
.PHONY: phasher-release-static-windows
phasher-release-static-windows: flags-release
phasher-release-static-windows: flags-static-windows
phasher-release-static-windows: phasher
# builds dynamically-linked debug binaries
.PHONY: build
build: stash
build: stash phasher
# builds dynamically-linked PIE release binaries
# builds dynamically-linked release binaries
.PHONY: build-release
build-release: flags-release flags-pie build
build-release: stash-release phasher-release
# compile and bundle into Stash.app
# for when on macOS itself
.PHONY: stash-macapp
stash-macapp: STASH_OUTPUT := -o stash
stash-macapp: flags-release flags-pie stash
rm -rf Stash.app
cp -R scripts/macos-bundle Stash.app
mkdir Stash.app/Contents/MacOS
cp stash Stash.app/Contents/MacOS/stash
# builds statically-linked release binaries
.PHONY: build-release-static
build-release-static: stash-release-static phasher-release-static
# build-cc- targets should be run within the compiler docker container
# build-release-static, but excluding netgo, which is not needed on windows
.PHONY: build-release-static-windows
build-release-static-windows: stash-release-static-windows phasher-release-static-windows
.PHONY: build-cc-windows
build-cc-windows: export GOOS := windows
build-cc-windows: export GOARCH := amd64
build-cc-windows: export CC := x86_64-w64-mingw32-gcc
build-cc-windows: STASH_OUTPUT := -o dist/stash-win.exe
build-cc-windows: PHASHER_OUTPUT :=-o dist/phasher-win.exe
build-cc-windows: flags-release
build-cc-windows: flags-static-windows
build-cc-windows: build
# cross-compile- targets should be run within the compiler docker container
.PHONY: cross-compile-windows
cross-compile-windows: export GOOS := windows
cross-compile-windows: export GOARCH := amd64
cross-compile-windows: export CC := x86_64-w64-mingw32-gcc
cross-compile-windows: export CXX := x86_64-w64-mingw32-g++
cross-compile-windows: STASH_OUTPUT := -o dist/stash-win.exe
cross-compile-windows: PHASHER_OUTPUT := -o dist/phasher-win.exe
cross-compile-windows: flags-release
cross-compile-windows: flags-static-windows
cross-compile-windows: build
.PHONY: build-cc-macos-intel
build-cc-macos-intel: export GOOS := darwin
build-cc-macos-intel: export GOARCH := amd64
build-cc-macos-intel: export CC := o64-clang
build-cc-macos-intel: STASH_OUTPUT := -o dist/stash-macos-intel
build-cc-macos-intel: PHASHER_OUTPUT := -o dist/phasher-macos-intel
build-cc-macos-intel: flags-release
# can't use static build for macOS
build-cc-macos-intel: flags-pie
build-cc-macos-intel: build
.PHONY: cross-compile-macos-intel
cross-compile-macos-intel: export GOOS := darwin
cross-compile-macos-intel: export GOARCH := amd64
cross-compile-macos-intel: export CC := o64-clang
cross-compile-macos-intel: export CXX := o64-clang++
cross-compile-macos-intel: STASH_OUTPUT := -o dist/stash-macos-intel
cross-compile-macos-intel: PHASHER_OUTPUT := -o dist/phasher-macos-intel
cross-compile-macos-intel: flags-release
# can't use static build for OSX
cross-compile-macos-intel: flags-pie
cross-compile-macos-intel: build
.PHONY: build-cc-macos-arm
build-cc-macos-arm: export GOOS := darwin
build-cc-macos-arm: export GOARCH := arm64
build-cc-macos-arm: export CC := oa64e-clang
build-cc-macos-arm: STASH_OUTPUT := -o dist/stash-macos-arm
build-cc-macos-arm: PHASHER_OUTPUT := -o dist/phasher-macos-arm
build-cc-macos-arm: flags-release
# can't use static build for macOS
build-cc-macos-arm: flags-pie
build-cc-macos-arm: build
.PHONY: build-cc-macos
build-cc-macos:
make build-cc-macos-arm
make build-cc-macos-intel
# Combine into universal binaries
lipo -create -output dist/stash-macos dist/stash-macos-intel dist/stash-macos-arm
rm dist/stash-macos-intel dist/stash-macos-arm
.PHONY: cross-compile-macos-applesilicon
cross-compile-macos-applesilicon: export GOOS := darwin
cross-compile-macos-applesilicon: export GOARCH := arm64
cross-compile-macos-applesilicon: export CC := oa64e-clang
cross-compile-macos-applesilicon: export CXX := oa64e-clang++
cross-compile-macos-applesilicon: STASH_OUTPUT := -o dist/stash-macos-applesilicon
cross-compile-macos-applesilicon: PHASHER_OUTPUT := -o dist/phasher-macos-applesilicon
cross-compile-macos-applesilicon: flags-release
# can't use static build for OSX
cross-compile-macos-applesilicon: flags-pie
cross-compile-macos-applesilicon: build
.PHONY: cross-compile-macos
cross-compile-macos:
rm -rf dist/Stash.app dist/Stash-macos.zip
make cross-compile-macos-applesilicon
make cross-compile-macos-intel
# Combine into one universal binary
lipo -create -output dist/stash-macos-universal dist/stash-macos-intel dist/stash-macos-applesilicon
rm dist/stash-macos-intel dist/stash-macos-applesilicon
# Place into bundle and zip up
rm -rf dist/Stash.app
cp -R scripts/macos-bundle dist/Stash.app
mkdir dist/Stash.app/Contents/MacOS
cp dist/stash-macos dist/Stash.app/Contents/MacOS/stash
cd dist && rm -f Stash.app.zip && zip -r Stash.app.zip Stash.app
mv dist/stash-macos-universal dist/Stash.app/Contents/MacOS/stash
cd dist && zip -r Stash-macos.zip Stash.app && cd ..
rm -rf dist/Stash.app
.PHONY: build-cc-macos-phasher
build-cc-macos-phasher:
make build-cc-macos-arm
make build-cc-macos-intel
.PHONY: cross-compile-freebsd
cross-compile-freebsd: export GOOS := freebsd
cross-compile-freebsd: export GOARCH := amd64
cross-compile-freebsd: STASH_OUTPUT := -o dist/stash-freebsd
cross-compile-freebsd: PHASHER_OUTPUT := -o dist/phasher-freebsd
cross-compile-freebsd: flags-release
cross-compile-freebsd: flags-static-pie
cross-compile-freebsd: build
# Combine into universal binaries
lipo -create -output dist/phasher-macos dist/phasher-macos-intel dist/phasher-macos-arm
rm dist/phasher-macos-intel dist/phasher-macos-arm
# do not bundle phasher
.PHONY: cross-compile-linux
cross-compile-linux: export GOOS := linux
cross-compile-linux: export GOARCH := amd64
cross-compile-linux: STASH_OUTPUT := -o dist/stash-linux
cross-compile-linux: PHASHER_OUTPUT := -o dist/phasher-linux
cross-compile-linux: flags-release
cross-compile-linux: flags-static-pie
cross-compile-linux: build
.PHONY: build-cc-freebsd
build-cc-freebsd: export GOOS := freebsd
build-cc-freebsd: export GOARCH := amd64
build-cc-freebsd: export CC := clang -target x86_64-unknown-freebsd12.0 --sysroot=/opt/cross-freebsd
build-cc-freebsd: STASH_OUTPUT := -o dist/stash-freebsd
build-cc-freebsd: PHASHER_OUTPUT := -o dist/phasher-freebsd
build-cc-freebsd: flags-release
build-cc-freebsd: flags-static-pie
build-cc-freebsd: build
.PHONY: cross-compile-linux-arm64v8
cross-compile-linux-arm64v8: export GOOS := linux
cross-compile-linux-arm64v8: export GOARCH := arm64
cross-compile-linux-arm64v8: export CC := aarch64-linux-gnu-gcc
cross-compile-linux-arm64v8: STASH_OUTPUT := -o dist/stash-linux-arm64v8
cross-compile-linux-arm64v8: PHASHER_OUTPUT := -o dist/phasher-linux-arm64v8
cross-compile-linux-arm64v8: flags-release
cross-compile-linux-arm64v8: flags-static-pie
cross-compile-linux-arm64v8: build
.PHONY: build-cc-linux
build-cc-linux: export GOOS := linux
build-cc-linux: export GOARCH := amd64
build-cc-linux: STASH_OUTPUT := -o dist/stash-linux
build-cc-linux: PHASHER_OUTPUT := -o dist/phasher-linux
build-cc-linux: flags-release
build-cc-linux: flags-static-pie
build-cc-linux: build
.PHONY: cross-compile-linux-arm32v7
cross-compile-linux-arm32v7: export GOOS := linux
cross-compile-linux-arm32v7: export GOARCH := arm
cross-compile-linux-arm32v7: export GOARM := 7
cross-compile-linux-arm32v7: export CC := arm-linux-gnueabihf-gcc
cross-compile-linux-arm32v7: STASH_OUTPUT := -o dist/stash-linux-arm32v7
cross-compile-linux-arm32v7: PHASHER_OUTPUT := -o dist/phasher-linux-arm32v7
cross-compile-linux-arm32v7: flags-release
cross-compile-linux-arm32v7: flags-static
cross-compile-linux-arm32v7: build
.PHONY: build-cc-linux-arm64v8
build-cc-linux-arm64v8: export GOOS := linux
build-cc-linux-arm64v8: export GOARCH := arm64
build-cc-linux-arm64v8: export CC := aarch64-linux-gnu-gcc
build-cc-linux-arm64v8: STASH_OUTPUT := -o dist/stash-linux-arm64v8
build-cc-linux-arm64v8: PHASHER_OUTPUT := -o dist/phasher-linux-arm64v8
build-cc-linux-arm64v8: flags-release
build-cc-linux-arm64v8: flags-static-pie
build-cc-linux-arm64v8: build
.PHONY: cross-compile-linux-arm32v6
cross-compile-linux-arm32v6: export GOOS := linux
cross-compile-linux-arm32v6: export GOARCH := arm
cross-compile-linux-arm32v6: export GOARM := 6
cross-compile-linux-arm32v6: export CC := arm-linux-gnueabi-gcc
cross-compile-linux-arm32v6: STASH_OUTPUT := -o dist/stash-linux-arm32v6
cross-compile-linux-arm32v6: PHASHER_OUTPUT := -o dist/phasher-linux-arm32v6
cross-compile-linux-arm32v6: flags-release
cross-compile-linux-arm32v6: flags-static
cross-compile-linux-arm32v6: build
.PHONY: build-cc-linux-arm32v7
build-cc-linux-arm32v7: export GOOS := linux
build-cc-linux-arm32v7: export GOARCH := arm
build-cc-linux-arm32v7: export GOARM := 7
build-cc-linux-arm32v7: export CC := arm-linux-gnueabi-gcc -march=armv7-a
build-cc-linux-arm32v7: STASH_OUTPUT := -o dist/stash-linux-arm32v7
build-cc-linux-arm32v7: PHASHER_OUTPUT := -o dist/phasher-linux-arm32v7
build-cc-linux-arm32v7: flags-release
build-cc-linux-arm32v7: flags-static
build-cc-linux-arm32v7: build
.PHONY: build-cc-linux-arm32v6
build-cc-linux-arm32v6: export GOOS := linux
build-cc-linux-arm32v6: export GOARCH := arm
build-cc-linux-arm32v6: export GOARM := 6
build-cc-linux-arm32v6: export CC := arm-linux-gnueabi-gcc
build-cc-linux-arm32v6: STASH_OUTPUT := -o dist/stash-linux-arm32v6
build-cc-linux-arm32v6: PHASHER_OUTPUT := -o dist/phasher-linux-arm32v6
build-cc-linux-arm32v6: flags-release
build-cc-linux-arm32v6: flags-static
build-cc-linux-arm32v6: build
.PHONY: build-cc-all
build-cc-all:
make build-cc-windows
make build-cc-macos
make build-cc-linux
make build-cc-linux-arm64v8
make build-cc-linux-arm32v7
make build-cc-linux-arm32v6
make build-cc-freebsd
.PHONY: cross-compile-all
cross-compile-all:
make cross-compile-windows
make cross-compile-macos-intel
make cross-compile-macos-applesilicon
make cross-compile-linux
make cross-compile-linux-arm64v8
make cross-compile-linux-arm32v7
make cross-compile-linux-arm32v6
.PHONY: touch-ui
touch-ui:
@@ -283,16 +282,12 @@ generate: generate-backend generate-ui
.PHONY: generate-ui
generate-ui:
cd ui/v2.5 && npm run gqlgen
cd ui/v2.5 && yarn run gqlgen
.PHONY: generate-backend
generate-backend: touch-ui
go generate ./cmd/stash
.PHONY: generate-login-locale
generate-login-locale:
go generate ./ui
.PHONY: generate-dataloaders
generate-dataloaders:
go generate ./internal/api/loaders
@@ -319,13 +314,12 @@ test:
# runs all tests - including integration tests
.PHONY: it
it:
$(eval GO_BUILD_TAGS += integration)
go test -tags "$(GO_BUILD_TAGS)" ./...
go test -tags=integration ./...
# generates test mocks
.PHONY: generate-test-mocks
generate-test-mocks:
go run github.com/vektra/mockery/v2
go run github.com/vektra/mockery/v2 --dir ./pkg/models --name '.*ReaderWriter' --outpkg mocks --output ./pkg/models/mocks
# runs server
# sets the config file to use the local dev config
@@ -346,19 +340,9 @@ server-clean:
# installs UI dependencies. Run when first cloning repository, or if UI
# dependencies have changed
# If CI is set, configures pnpm to use a local store to avoid
# putting .pnpm-store in /stash
# NOTE: to run in the docker build container, using the existing
# node_modules folder, rename the .modules.yaml to .modules.yaml.bak
# and a new one will be generated. This will need to be reversed after
# building.
.PHONY: pre-ui
pre-ui:
ifdef CI
cd ui/v2.5 && pnpm config set store-dir ~/.pnpm-store && pnpm install --frozen-lockfile
else
cd ui/v2.5 && pnpm install --frozen-lockfile
endif
cd ui/v2.5 && yarn install --frozen-lockfile
.PHONY: ui-env
ui-env: build-info
@@ -373,47 +357,29 @@ ifdef STASH_SOURCEMAPS
endif
.PHONY: ui
ui: ui-only generate-login-locale
ui: ui-env
cd ui/v2.5 && yarn build
.PHONY: ui-only
ui-only: ui-env
cd ui/v2.5 && npm run build
.PHONY: ui-nolegacy
ui-nolegacy: STASH_NOLEGACY := true
ui-nolegacy: ui
.PHONY: zip-ui
zip-ui:
rm -f dist/stash-ui.zip
cd ui/v2.5/build && zip -r ../../../dist/stash-ui.zip .
.PHONY: ui-sourcemaps
ui-sourcemaps: STASH_SOURCEMAPS := true
ui-sourcemaps: ui
.PHONY: ui-start
ui-start: ui-env
cd ui/v2.5 && npm run start -- --host
cd ui/v2.5 && yarn start --host
.PHONY: fmt-ui
fmt-ui:
cd ui/v2.5 && npm run format
cd ui/v2.5 && yarn format
# runs all of the frontend PR-acceptance steps
.PHONY: validate-ui
validate-ui:
cd ui/v2.5 && npm run validate
# these targets run the same steps as fmt-ui and validate-ui, but only on files that have changed
fmt-ui-quick:
cd ui/v2.5 && \
files=$$(git diff --name-only --relative --diff-filter d . ../../graphql); \
if [ -n "$$files" ]; then \
npm run prettier -- --write $$files; \
fi
# does not run tsc checks, as they are slow
validate-ui-quick:
cd ui/v2.5 && \
tsfiles=$$(git diff --name-only --relative --diff-filter d src | grep -e "\.tsx\?\$$"); \
scssfiles=$$(git diff --name-only --relative --diff-filter d src | grep "\.scss"); \
prettyfiles=$$(git diff --name-only --relative --diff-filter d . ../../graphql); \
if [ -n "$$tsfiles" ]; then npm run eslint -- $$tsfiles; fi && \
if [ -n "$$scssfiles" ]; then npm run stylelint -- $$scssfiles; fi && \
if [ -n "$$prettyfiles" ]; then npm run prettier -- --check $$prettyfiles; fi
cd ui/v2.5 && yarn run validate
# runs all of the backend PR-acceptance steps
.PHONY: validate-backend
@@ -432,16 +398,3 @@ docker-build: build-info
.PHONY: docker-cuda-build
docker-cuda-build: build-info
docker build --build-arg GITHASH=$(GITHASH) --build-arg STASH_VERSION=$(STASH_VERSION) -t stash/cuda-build -f docker/build/x86_64/Dockerfile-CUDA .
# start the build container - for cross compilation
# this is adapted from the github actions build.yml file
.PHONY: start-compiler-container
start-compiler-container:
docker run -d --name build --mount type=bind,source="$(PWD)",target=/stash,consistency=delegated $(EXTRA_CONTAINER_ARGS) -w /stash $(COMPILER_IMAGE) tail -f /dev/null
# run the cross-compilation using
# docker exec -t build /bin/bash -c "make build-cc-<platform>"
.PHONY: remove-compiler-container
remove-compiler-container:
docker rm -f -v build

View File

@@ -5,105 +5,70 @@
[![GitHub Sponsors](https://img.shields.io/github/sponsors/stashapp?logo=github)](https://github.com/sponsors/stashapp)
[![Open Collective backers](https://img.shields.io/opencollective/backers/stashapp?logo=opencollective)](https://opencollective.com/stashapp)
[![Go Report Card](https://goreportcard.com/badge/github.com/stashapp/stash)](https://goreportcard.com/report/github.com/stashapp/stash)
[![Matrix](https://img.shields.io/matrix/stashapp:unredacted.org?logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#stashapp:unredacted.org)
[![Discord](https://img.shields.io/discord/559159668438728723.svg?logo=discord)](https://discord.gg/2TsNFKt)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/stashapp/stash?logo=github)](https://github.com/stashapp/stash/releases/latest)
[![GitHub issues by-label](https://img.shields.io/github/issues-raw/stashapp/stash/bounty)](https://github.com/stashapp/stash/labels/bounty)
### **Stash is a self-hosted webapp written in Go which organizes and serves your diverse content collection, catering to both your SFW and NSFW needs.**
### **Stash is a self-hosted webapp written in Go which organizes and serves your porn.**
![demo image](docs/readme_assets/demo_image.png)
![Screenshot of Stash web application interface](docs/readme_assets/demo_image.png)
- Stash gathers information about videos in your collection from the internet, and is extensible through the use of community-built plugins for a large number of content producers and sites.
- Stash supports a wide variety of both video and image formats.
- You can tag videos and find them later.
- Stash provides statistics about performers, tags, studios and more.
* Stash gathers information about videos in your collection from the internet, and is extensible through the use of community-built plugins for a large number of content producers and sites.
* Stash supports a wide variety of both video and image formats.
* You can tag videos and find them later.
* Stash provides statistics about performers, tags, studios and more.
You can [watch a SFW demo video](https://vimeo.com/545323354) to see it in action.
For further information you can consult the [documentation](https://docs.stashapp.cc) or access the in-app manual from within the application (also available at [docs.stashapp.cc/in-app-manual](https://docs.stashapp.cc/in-app-manual)).
For further information you can consult the [documentation](https://docs.stashapp.cc) or [read the in-app manual](ui/v2.5/src/docs/en).
# Installing Stash
> [!tip]
Step-by-step instructions are available at [docs.stashapp.cc/installation](https://docs.stashapp.cc/installation/).
> [!important]
>**Windows Users**
>
>As of version 0.27.0, Stash no longer supports _Windows 7, 8, Server 2008 and Server 2012._
>At least Windows 10 or Server 2016 is required.
>
>**macOS Users**
>
> As of version 0.29.0, Stash requires _macOS 11 Big Sur_ or later.
> Stash can still be run through docker on older versions of macOS.
<img src="docs/readme_assets/windows_logo.svg" width="100%" height="75"> Windows | <img src="docs/readme_assets/mac_logo.svg" width="100%" height="75"> macOS | <img src="docs/readme_assets/linux_logo.svg" width="100%" height="75"> Linux | <img src="docs/readme_assets/docker_logo.svg" width="100%" height="75"> Docker
<img src="docs/readme_assets/windows_logo.svg" width="100%" height="75"> Windows | <img src="docs/readme_assets/mac_logo.svg" width="100%" height="75"> MacOS| <img src="docs/readme_assets/linux_logo.svg" width="100%" height="75"> Linux | <img src="docs/readme_assets/docker_logo.svg" width="100%" height="75"> Docker
:---:|:---:|:---:|:---:
[Latest Release](https://github.com/stashapp/stash/releases/latest/download/stash-win.exe) <br /> <sup><sub>[Development Preview](https://github.com/stashapp/stash/releases/download/latest_develop/stash-win.exe)</sub></sup> | [Latest Release](https://github.com/stashapp/stash/releases/latest/download/Stash.app.zip) <br /> <sup><sub>[Development Preview](https://github.com/stashapp/stash/releases/download/latest_develop/Stash.app.zip)</sub></sup> | [Latest Release (amd64)](https://github.com/stashapp/stash/releases/latest/download/stash-linux) <br /> <sup><sub>[Development Preview (amd64)](https://github.com/stashapp/stash/releases/download/latest_develop/stash-linux)</sub></sup> <br /> [More Architectures...](https://github.com/stashapp/stash/releases/latest) | [Instructions](docker/production/README.md) <br /> <sup><sub>[Sample docker-compose.yml](docker/production/docker-compose.yml)</sub></sup>
Download links for other platforms and architectures are available on the [Releases](https://github.com/stashapp/stash/releases) page.
[Latest Release](https://github.com/stashapp/stash/releases/latest/download/stash-win.exe) <br /> <sup><sub>[Development Preview](https://github.com/stashapp/stash/releases/download/latest_develop/stash-win.exe)</sub></sup> | [Latest Release (Apple Silicon)](https://github.com/stashapp/stash/releases/latest/download/stash-macos-applesilicon) <br /> <sup><sub>[Development Preview (Apple Silicon)](https://github.com/stashapp/stash/releases/download/latest_develop/stash-macos-applesilicon)</sub></sup> <br />[Latest Release (Intel)](https://github.com/stashapp/stash/releases/latest/download/stash-macos-intel) <br /> <sup><sub>[Development Preview (Intel)](https://github.com/stashapp/stash/releases/download/latest_develop/stash-macos-intel)</sub></sup> | [Latest Release (amd64)](https://github.com/stashapp/stash/releases/latest/download/stash-linux) <br /> <sup><sub>[Development Preview (amd64)](https://github.com/stashapp/stash/releases/download/latest_develop/stash-linux)</sub></sup> <br /> [More Architectures...](https://github.com/stashapp/stash/releases/latest) | [Instructions](docker/production/README.md) <br /> <sup><sub> [Sample docker-compose.yml](docker/production/docker-compose.yml)</sub></sup>
## First Run
#### Windows/macOS Users: Security Prompt
On Windows or macOS, running the app might present a security prompt since the application binary isn't yet signed.
- On Windows, bypass this by clicking "more info" and then the "run anyway" button.
- On macOS, Control+Click the app, click "Open", and then "Open" again.
#### ffmpeg
Stash requires FFmpeg. If you don't have it installed, Stash will prompt you to download a copy during setup. It is recommended that Linux users install `ffmpeg` from their distro's package manager.
#### Windows Users: Security Prompt
Running the app might present a security prompt since the binary isn't yet signed. Bypass this by clicking "more info" and then the "run anyway" button.
#### FFMPEG
Stash requires ffmpeg. If you don't have it installed, Stash will download a copy for you. It is recommended that Linux users install `ffmpeg` from their distro's package manager.
# Usage
## Quickstart Guide
Stash is a web-based application. Once the application is running, the interface is available (by default) from `http://localhost:9999`.
Stash is a web-based application. Once the application is running, the interface is available (by default) from http://localhost:9999.
On first run, Stash will prompt you for some configuration options and media directories to index, called "Scanning" in Stash. After scanning, your media will be available for browsing, curating, editing, and tagging.
Stash can pull metadata (performers, tags, descriptions, studios, and more) directly from many sites through the use of [scrapers](https://github.com/stashapp/stash/blob/develop/ui/v2.5/src/docs/en/Manual/Scraping.md), which integrate directly into Stash. Identifying an entire collection will typically require a mix of multiple sources:
- The stashapp team maintains [StashDB](https://stashdb.org/), a crowd-sourced repository of scene, studio, and performer information. Connecting it to Stash will allow you to automatically identify much of a typical media collection. It runs on our stash-box software and is primarily focused on mainstream digital scenes and studios. Instructions, invite codes, and more can be found in this guide to [Accessing StashDB](https://guidelines.stashdb.org/docs/faq_getting-started/stashdb/accessing-stashdb/).
- Several community-managed stash-box databases can also be connected to Stash in a similar manner. Each one serves a slightly different niche and follows their own methodology. A rundown of each stash-box, their differences, and the information you need to sign up can be found in this guide to [Accessing Stash-Boxes](https://guidelines.stashdb.org/docs/faq_getting-started/stashdb/accessing-stash-boxes/).
- Many community-maintained scrapers can also be downloaded, installed, and updated from within Stash, allowing you to pull data from a wide range of other websites and databases. They can be found by navigating to `Settings → Metadata Providers → Available Scrapers → Community (stable)`. These can be trickier to use than a stash-box because every scraper works a little differently. For more information, please visit the [CommunityScrapers repository](https://github.com/stashapp/CommunityScrapers).
- All of the above methods of scraping data into Stash are also covered in more detail in our [Guide to Scraping](https://docs.stashapp.cc/beginner-guides/guide-to-scraping/).
Stash can pull metadata (performers, tags, descriptions, studios, and more) directly from many sites through the use of [scrapers](https://github.com/stashapp/stash/tree/develop/ui/v2.5/src/docs/en/Scraping.md), which integrate directly into Stash.
Many community-maintained scrapers are available for download from [CommunityScrapers repository](https://github.com/stashapp/CommunityScrapers). The community also maintains StashDB, a crowd-sourced repository of scene, studio, and performer information, that can automatically identify much of a typical media collection. Inquire in the Discord for details. Identifying an entire collection will typically require a mix of multiple sources.
<sub>[StashDB](http://stashdb.org) is the canonical instance of our open source metadata API, [stash-box](https://github.com/stashapp/stash-box).</sub>
# Translation
[![Translate](https://hosted.weblate.org/widget/stashapp/stash/svg-badge.svg)](https://hosted.weblate.org/engage/stashapp/)
🇧🇷 🇨🇳 🇩🇰 🇳🇱 🇬🇧 🇪🇪 🇫🇮 🇫🇷 🇩🇪 🇮🇹 🇯🇵 🇰🇷 🇵🇱 🇷🇺 🇪🇸 🇸🇪 🇹🇼 🇹🇷
[![Translate](https://translate.codeberg.org/widget/stash/stash/svg-badge.svg)](https://translate.codeberg.org/engage/stash/)
Stash is available in 25 languages (so far!) and it could be in your language too. We use Weblate to coordinate community translations. If you want to help us translate Stash into your language, you can make an account at [Stash's Weblate](https://hosted.weblate.org/projects/stashapp/stash/) to get started contributing new languages or improving existing ones. Thanks!
Stash is available in 32 languages (so far!) and it could be in your language too. We use Weblate to coordinate community translations. If you want to help us translate Stash, you can make an account at [Codeberg's Weblate](https://translate.codeberg.org/projects/stash/stash/) to contribute to new or existing languages. Thanks!
# Support (FAQ)
The badge below shows the current translation status of Stash across all supported languages:
Check out our documentation on [Stash-Docs](https://docs.stashapp.cc) for information about the software, questions, guides, add-ons and more.
[![Translation status](https://translate.codeberg.org/widget/stash/stash/multi-auto.svg)](https://translate.codeberg.org/engage/stash/)
For more help you can:
* Check the in-app documentation, in the top right corner of the app (it's also mirrored on [Stash-Docs](https://docs.stashapp.cc/in-app-manual))
* Join the [Matrix space](https://matrix.to/#/#stashapp:unredacted.org)
* Join the [Discord server](https://discord.gg/2TsNFKt), where the community can offer support.
* Start a [discussion on GitHub](https://github.com/stashapp/stash/discussions)
# Support & Resources
# Customization
Need help or want to get involved? Start with the documentation, then reach out to the community if you need further assistance.
## Themes and CSS Customization
There is a [directory of community-created themes](https://docs.stashapp.cc/user-interface-ui/themes) on Stash-Docs, along with instructions on how to install them.
### Documentation
- [Official documentation](https://docs.stashapp.cc) - official guides guides and troubleshooting.
- [In-app manual](https://docs.stashapp.cc/in-app-manual) press <kbd>Shift</kbd> + <kbd>?</kbd> in the app or view the manual online.
- [FAQ](https://discourse.stashapp.cc/c/support/faq/28) - common questions and answers.
- [Community wiki](https://discourse.stashapp.cc/tags/c/community-wiki/22/stash) - guides, how-tos and tips.
### Community & discussion
- [Community forum](https://discourse.stashapp.cc) - community support, feature requests and discussions.
- [Discord](https://discord.gg/2TsNFKt) - real-time chat and community support.
- [GitHub discussions](https://github.com/stashapp/stash/discussions) - community support and feature discussions.
- [Lemmy community](https://discuss.online/c/stashapp) - board-style community space.
### Community scrapers & plugins
- [Metadata sources](https://docs.stashapp.cc/metadata-sources/)
- [Plugins](https://docs.stashapp.cc/plugins/)
- [Themes](https://docs.stashapp.cc/themes/)
- [Other projects](https://docs.stashapp.cc/other-projects/)
You can also change the Stash interface to fit your desired style with various snippets from [Custom CSS snippets](https://docs.stashapp.cc/user-interface-ui/custom-css-snippets).
# For Developers

View File

@@ -2,42 +2,23 @@
package main
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
flag "github.com/spf13/pflag"
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/hash/imagephash"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/hash/videophash"
"github.com/stashapp/stash/pkg/models"
)
func customUsage() {
fmt.Fprintf(os.Stderr, "Usage:\n")
fmt.Fprintf(os.Stderr, "%s [OPTIONS] FILE...\n\nOptions:\n", os.Args[0])
fmt.Fprintf(os.Stderr, "%s [OPTIONS] VIDEOFILE...\n\nOptions:\n", os.Args[0])
flag.PrintDefaults()
}
func printPhash(ff *ffmpeg.FFMpeg, ffp *ffmpeg.FFProbe, inputfile string, quiet *bool) error {
// Determine if this is a video or image file based on extension
ext := filepath.Ext(inputfile)
ext = ext[1:] // remove the leading dot
// Common image extensions
imageExts := map[string]bool{
"jpg": true, "jpeg": true, "png": true, "gif": true, "webp": true, "bmp": true, "avif": true,
}
if imageExts[ext] {
return printImagePhash(ff, inputfile, quiet)
}
return printVideoPhash(ff, ffp, inputfile, quiet)
}
func printVideoPhash(ff *ffmpeg.FFMpeg, ffp *ffmpeg.FFProbe, inputfile string, quiet *bool) error {
func printPhash(ff *ffmpeg.FFMpeg, ffp ffmpeg.FFProbe, inputfile string, quiet *bool) error {
ffvideoFile, err := ffp.NewVideoFile(inputfile)
if err != nil {
return err
@@ -47,8 +28,8 @@ func printVideoPhash(ff *ffmpeg.FFMpeg, ffp *ffmpeg.FFProbe, inputfile string, q
// videoFile.Path (from BaseFile)
// videoFile.Duration
// The rest of the struct isn't needed.
vf := &models.VideoFile{
BaseFile: &models.BaseFile{Path: inputfile},
vf := &file.VideoFile{
BaseFile: &file.BaseFile{Path: inputfile},
Duration: ffvideoFile.FileDuration,
}
@@ -65,31 +46,6 @@ func printVideoPhash(ff *ffmpeg.FFMpeg, ffp *ffmpeg.FFProbe, inputfile string, q
return nil
}
func printImagePhash(ff *ffmpeg.FFMpeg, inputfile string, quiet *bool) error {
imgFile := &models.ImageFile{
BaseFile: &models.BaseFile{Path: inputfile},
}
phash, err := imagephash.Generate(ff, imgFile)
if err != nil {
return err
}
if *quiet {
fmt.Printf("%x\n", *phash)
} else {
fmt.Printf("%x %v\n", *phash, imgFile.Path)
}
return nil
}
func getPaths() (string, string) {
ffmpegPath, _ := exec.LookPath("ffmpeg")
ffprobePath, _ := exec.LookPath("ffprobe")
return ffmpegPath, ffprobePath
}
func main() {
flag.Usage = customUsage
quiet := flag.BoolP("quiet", "q", false, "print only the phash")
@@ -104,25 +60,24 @@ func main() {
args := flag.Args()
if len(args) < 1 {
fmt.Fprintf(os.Stderr, "Missing FILE argument.\n")
fmt.Fprintf(os.Stderr, "Missing VIDEOFILE argument.\n")
flag.Usage()
os.Exit(2)
}
if len(args) > 1 {
fmt.Fprintln(os.Stderr, "Files will be processed sequentially! If required, use e.g. GNU Parallel to run concurrently.")
fmt.Fprintln(os.Stderr, "Files will be processed sequentially! Consier using GNU Parallel.")
fmt.Fprintf(os.Stderr, "Example: parallel %v ::: *.mp4\n", os.Args[0])
}
ffmpegPath, ffprobePath := getPaths()
ffmpegPath, ffprobePath := ffmpeg.GetPaths(nil)
encoder := ffmpeg.NewEncoder(ffmpegPath)
// don't need to InitHWSupport, phashing doesn't use hw acceleration
ffprobe := ffmpeg.NewFFProbe(ffprobePath)
encoder.InitHWSupport(context.TODO())
ffprobe := ffmpeg.FFProbe(ffprobePath)
for _, item := range args {
if err := printPhash(encoder, ffprobe, item, quiet); err != nil {
fmt.Fprintln(os.Stderr, err)
}
}
}

View File

@@ -1,174 +1,68 @@
//go:generate go run github.com/99designs/gqlgen
//go:generate go run -mod=vendor github.com/99designs/gqlgen
package main
import (
"errors"
"fmt"
"net/http"
"os"
"os/signal"
"runtime/debug"
"runtime/pprof"
"syscall"
"github.com/spf13/pflag"
"github.com/stashapp/stash/internal/api"
"github.com/stashapp/stash/internal/build"
"github.com/stashapp/stash/internal/desktop"
"github.com/stashapp/stash/internal/log"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/ui"
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
var exitCode = 0
func main() {
defer func() {
if exitCode != 0 {
os.Exit(exitCode)
}
}()
defer recoverPanic()
initLogTemp()
helpFlag := false
pflag.BoolVarP(&helpFlag, "help", "h", false, "show this help text and exit")
versionFlag := false
pflag.BoolVarP(&versionFlag, "version", "v", false, "show version number and exit")
cpuProfilePath := ""
pflag.StringVar(&cpuProfilePath, "cpuprofile", "", "write cpu profile to file")
pflag.Parse()
if helpFlag {
pflag.Usage()
return
}
if versionFlag {
fmt.Println(build.VersionString())
return
}
cfg, err := config.Initialize()
_, err := manager.Initialize()
if err != nil {
exitError(fmt.Errorf("config initialization error: %w", err))
return
panic(err)
}
l := initLog(cfg)
if cpuProfilePath != "" {
if err := initProfiling(cpuProfilePath); err != nil {
exitError(err)
return
}
defer pprof.StopCPUProfile()
}
// initialise desktop.IsDesktop here so that it doesn't get affected by
// ffmpeg hardware checks later on
desktop.InitIsDesktop()
mgr, err := manager.Initialize(cfg, l)
if err != nil {
exitError(fmt.Errorf("manager initialization error: %w", err))
return
}
defer mgr.Shutdown()
server, err := api.Initialize()
if err != nil {
exitError(fmt.Errorf("api initialization error: %w", err))
return
}
defer server.Shutdown()
exit := make(chan int)
go func() {
err := server.Start()
if !errors.Is(err, http.ErrServerClosed) {
exitError(fmt.Errorf("http server error: %w", err))
exit <- 1
defer recoverPanic()
if err := api.Start(); err != nil {
handleError(err)
} else {
manager.GetInstance().Shutdown(0)
}
}()
go handleSignals(exit)
desktop.Start(exit, &ui.FaviconProvider)
go handleSignals()
desktop.Start(manager.GetInstance(), &ui.FaviconProvider)
exitCode = <-exit
}
// initLogTemp initializes a temporary logger for use before the config is loaded.
// Logs only error level message to stderr.
func initLogTemp() *log.Logger {
l := log.NewLogger()
l.Init("", true, "Error", 0)
logger.Logger = l
return l
}
func initLog(cfg *config.Config) *log.Logger {
l := log.NewLogger()
l.Init(cfg.GetLogFile(), cfg.GetLogOut(), cfg.GetLogLevel(), cfg.GetLogFileMaxSize())
logger.Logger = l
return l
}
func initProfiling(path string) error {
f, err := os.Create(path)
if err != nil {
return fmt.Errorf("unable to create CPU profile file: %v", err)
}
if err = pprof.StartCPUProfile(f); err != nil {
return fmt.Errorf("could not start CPU profiling: %v", err)
}
logger.Infof("profiling to %s", path)
return nil
blockForever()
}
func recoverPanic() {
if err := recover(); err != nil {
exitCode = 1
logger.Errorf("panic: %v\n%s", err, debug.Stack())
if desktop.IsDesktop() {
desktop.FatalError(fmt.Errorf("Panic: %v", err))
}
if p := recover(); p != nil {
handleError(fmt.Errorf("Panic: %v", p))
}
}
func exitError(err error) {
exitCode = 1
logger.Error(err)
// #5784 - log to stdout as well as the logger
// this does mean that it will log twice if the logger is set to stdout
fmt.Println(err)
func handleError(err error) {
if desktop.IsDesktop() {
desktop.FatalError(err)
manager.GetInstance().Shutdown(0)
} else {
panic(err)
}
}
func handleSignals(exit chan<- int) {
func handleSignals() {
// handle signals
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
<-signals
exit <- 0
manager.GetInstance().Shutdown(0)
}
func blockForever() {
select {}
}

View File

@@ -1,39 +1,35 @@
# This dockerfile should be built with `make docker-build` from the stash root.
# Build Frontend
FROM node:24-alpine AS frontend
FROM node:alpine as frontend
RUN apk add --no-cache make git
## cache node_modules separately
COPY ./ui/v2.5/package.json ./ui/v2.5/pnpm-lock.yaml /stash/ui/v2.5/
COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/
WORKDIR /stash
COPY Makefile /stash/
COPY ./graphql /stash/graphql/
COPY ./ui /stash/ui/
# pnpm install with npm
RUN npm install -g pnpm
RUN make pre-ui
RUN make generate-ui
ARG GITHASH
ARG STASH_VERSION
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui-only
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui
# Build Backend
FROM golang:1.24.3-alpine AS backend
FROM golang:1.19-alpine as backend
RUN apk add --no-cache make alpine-sdk
WORKDIR /stash
COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/
COPY ./graphql /stash/graphql/
COPY ./scripts /stash/scripts/
COPY ./vendor /stash/vendor/
COPY ./pkg /stash/pkg/
COPY ./cmd /stash/cmd/
COPY ./internal /stash/internal/
# needed for generate-login-locale
COPY ./ui /stash/ui/
RUN make generate-backend generate-login-locale
COPY ./cmd /stash/cmd
COPY ./internal /stash/internal
COPY --from=frontend /stash /stash/
RUN make generate-backend
ARG GITHASH
ARG STASH_VERSION
RUN make flags-release flags-pie stash
RUN make stash-release
# Final Runnable Image
FROM alpine:latest

View File

@@ -1,65 +1,51 @@
# This dockerfile should be built with `make docker-cuda-build` from the stash root.
ARG CUDA_VERSION=12.8.0
# Build Frontend
FROM node:20-alpine AS frontend
FROM node:alpine as frontend
RUN apk add --no-cache make git
## cache node_modules separately
COPY ./ui/v2.5/package.json ./ui/v2.5/pnpm-lock.yaml /stash/ui/v2.5/
COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/
WORKDIR /stash
COPY Makefile /stash/
COPY ./graphql /stash/graphql/
COPY ./ui /stash/ui/
# pnpm install with npm
RUN npm install -g pnpm
RUN make pre-ui
RUN make generate-ui
ARG GITHASH
ARG STASH_VERSION
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui-only
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui
# Build Backend
FROM golang:1.24.3-bullseye AS backend
FROM golang:1.19-bullseye as backend
RUN apt update && apt install -y build-essential golang
WORKDIR /stash
COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/
COPY ./graphql /stash/graphql/
COPY ./scripts /stash/scripts/
COPY ./vendor /stash/vendor/
COPY ./pkg /stash/pkg/
COPY ./cmd /stash/cmd
COPY ./internal /stash/internal
# needed for generate-login-locale
COPY ./ui /stash/ui/
RUN make generate-backend generate-login-locale
COPY --from=frontend /stash /stash/
RUN make generate-backend
ARG GITHASH
ARG STASH_VERSION
RUN make flags-release flags-pie stash
RUN make stash-release
# Final Runnable Image
FROM nvidia/cuda:${CUDA_VERSION}-base-ubuntu24.04
RUN apt update && apt upgrade -y && apt install -y \
# stash dependencies
ca-certificates libvips-tools ffmpeg \
# intel dependencies
intel-media-va-driver-non-free vainfo \
# python tools
python3 python3-pip && \
# cleanup
apt autoremove -y && apt clean && \
rm -rf /var/lib/apt/lists/*
COPY --from=backend --chmod=555 /stash/stash /usr/bin/
FROM nvidia/cuda:12.0.1-base-ubuntu22.04
RUN apt update && apt upgrade -y && apt install -y ca-certificates libvips-tools ffmpeg wget intel-media-va-driver-non-free vainfo
RUN rm -rf /var/lib/apt/lists/*
COPY --from=backend /stash/stash /usr/bin/
# NVENC Patch
RUN mkdir -p /usr/local/bin /patched-lib
ADD --chmod=555 https://raw.githubusercontent.com/keylase/nvidia-patch/master/patch.sh /usr/local/bin/patch.sh
ADD --chmod=555 https://raw.githubusercontent.com/keylase/nvidia-patch/master/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN wget https://raw.githubusercontent.com/keylase/nvidia-patch/master/patch.sh -O /usr/local/bin/patch.sh
RUN wget https://raw.githubusercontent.com/keylase/nvidia-patch/master/docker-entrypoint.sh -O /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/patch.sh /usr/local/bin/docker-entrypoint.sh /usr/bin/stash
ENV LANG=C.UTF-8
ENV NVIDIA_VISIBLE_DEVICES=all
ENV LANG C.UTF-8
ENV NVIDIA_VISIBLE_DEVICES all
ENV NVIDIA_DRIVER_CAPABILITIES=video,utility
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
EXPOSE 9999
ENTRYPOINT ["docker-entrypoint.sh", "stash"]
# vim: ft=dockerfile

View File

@@ -4,7 +4,7 @@ This dockerfile is used to build a stash docker container using the current sour
# Building the docker container
From the top-level directory (should contain `tools.go` file):
From the top-level directory (should contain `main.go` file):
```
make docker-build

View File

@@ -11,18 +11,12 @@ RUN if [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then BIN=stash-linux-arm32v6; \
FROM --platform=$TARGETPLATFORM alpine:latest AS app
COPY --from=binary /stash /usr/bin/
RUN apk add --no-cache ca-certificates python3 py3-requests py3-requests-toolbelt py3-lxml py3-pip ffmpeg tzdata vips vips-tools vips-heif \
&& pip install --break-system-packages mechanicalsoup cloudscraper stashapp-tools
RUN apk add --no-cache --virtual .build-deps gcc python3-dev musl-dev \
&& apk add --no-cache ca-certificates python3 py3-requests py3-requests-toolbelt py3-lxml py3-pip ffmpeg vips-tools ruby tzdata \
&& pip install mechanicalsoup cloudscraper bencoder.pyx \
&& gem install faraday \
&& apk del .build-deps
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
# Basic build-time metadata as defined at https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
LABEL org.opencontainers.image.title="Stash" \
org.opencontainers.image.description="An organizer for your porn, written in Go." \
org.opencontainers.image.url="https://stashapp.cc" \
org.opencontainers.image.documentation="https://docs.stashapp.cc" \
org.opencontainers.image.source="https://github.com/stashapp/stash" \
org.opencontainers.image.licenses="AGPL-3.0"
EXPOSE 9999
CMD ["stash"]

View File

@@ -1 +1 @@
This Dockerfile is used by CI to build the `stashapp/stash` Docker image. It must be run after cross-compiling - that is, `stash-linux` must exist in the `dist` directory. This image must be built from the `dist` directory.
This dockerfile is used by travis to build the stash image. It must be run after cross-compiling - that is, `stash-linux` must exist in the `dist` directory. This image must be built from the `dist` directory.

1
docker/compiler/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.sdk.tar.*

View File

@@ -1,86 +1,67 @@
### OSXCROSS
FROM debian:bookworm AS osxcross
# add osxcross
WORKDIR /tmp/osxcross
ARG OSXCROSS_REVISION=5e1b71fcceb23952f3229995edca1b6231525b5b
ADD --checksum=sha256:d3f771bbc20612fea577b18a71be3af2eb5ad2dd44624196cf55de866d008647 https://codeload.github.com/tpoechtrager/osxcross/tar.gz/${OSXCROSS_REVISION} /tmp/osxcross.tar.gz
FROM golang:1.19
ARG OSX_SDK_VERSION=11.3
ARG OSX_SDK_DOWNLOAD_FILE=MacOSX${OSX_SDK_VERSION}.sdk.tar.xz
ARG OSX_SDK_DOWNLOAD_URL=https://github.com/phracker/MacOSX-SDKs/releases/download/${OSX_SDK_VERSION}/${OSX_SDK_DOWNLOAD_FILE}
ADD --checksum=sha256:cd4f08a75577145b8f05245a2975f7c81401d75e9535dcffbb879ee1deefcbf4 ${OSX_SDK_DOWNLOAD_URL} /tmp/osxcross/tarballs/${OSX_SDK_DOWNLOAD_FILE}
LABEL maintainer="https://discord.gg/2TsNFKt"
ENV UNATTENDED=yes \
SDK_VERSION=${OSX_SDK_VERSION} \
OSX_VERSION_MIN=10.10
RUN apt update && \
apt install -y --no-install-recommends \
bash ca-certificates clang cmake git patch libssl-dev bzip2 cpio libbz2-dev libxml2-dev make python3 xz-utils zlib1g-dev
# lzma-dev libxml2-dev xz
RUN tar --strip=1 -C /tmp/osxcross -xf /tmp/osxcross.tar.gz
RUN ./build.sh
# Install tools
RUN apt-get update && apt-get install -y apt-transport-https
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
### FREEBSD cross-compilation stage
# use alpine for cacheable image since apt is notorous for not caching
FROM alpine:3 AS freebsd
# match golang latest
# https://go.dev/wiki/FreeBSD
ARG FREEBSD_VERSION=12.4
ADD --checksum=sha256:581c7edacfd2fca2bdf5791f667402d22fccd8a5e184635e0cac075564d57aa8 \
http://ftp-archive.freebsd.org/mirror/FreeBSD-Archive/old-releases/amd64/${FREEBSD_VERSION}-RELEASE/base.txz \
/tmp/base.txz
# prevent caching of the key
ADD https://dl.yarnpkg.com/debian/pubkey.gpg yarn.gpg
RUN cat yarn.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
rm yarn.gpg
WORKDIR /opt/cross-freebsd
RUN apk add --no-cache tar xz
RUN tar -xf /tmp/base.txz --strip-components=1 ./usr/lib ./usr/include ./lib
RUN cd /opt/cross-freebsd/usr/lib && \
find . -type l -exec sh -c ' \
for link; do \
target=$(readlink "$link"); \
case "$target" in \
/lib/*) ln -sf "/opt/cross-freebsd$target" "$link";; \
esac; \
done \
' sh {} + && \
ln -s libc++.a libstdc++.a && \
ln -s libc++.so libstdc++.so
### BUILDER
FROM golang:1.24.3 AS builder
ENV PATH=/opt/osx-ndk-x86/bin:$PATH
# copy in nodejs instead of using nodesource :thumbsup:
COPY --from=docker.io/library/node:24-bookworm /usr/local /usr/local
# copy in osxcross
COPY --from=osxcross /tmp/osxcross/target/lib /usr/lib
COPY --from=osxcross /tmp/osxcross/target /opt/osx-ndk-x86
# copy in cross-freebsd
COPY --from=freebsd /opt/cross-freebsd /opt/cross-freebsd
# pnpm install with npm
RUN npm install -g pnpm
# git for getting hash
# make and bash for building
# clang for macos
# zip for stashapp.zip
# gcc-extensions for cross-arch build
# we still target arm soft float?
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git make bash \
clang zip \
gcc-mingw-w64 \
gcc-arm-linux-gnueabi \
libc-dev-armel-cross linux-libc-dev-armel-cross \
gcc-aarch64-linux-gnu libc-dev-arm64-cross && \
rm -rf /var/lib/apt/lists/*;
RUN git config --global safe.directory '*'
apt-get install -y automake autogen cmake \
libtool libxml2-dev uuid-dev libssl-dev bash \
patch make tar xz-utils bzip2 gzip zlib1g-dev sed cpio \
gcc-10-multilib gcc-mingw-w64 g++-mingw-w64 clang llvm-dev \
gcc-arm-linux-gnueabi libc-dev-armel-cross linux-libc-dev-armel-cross \
gcc-arm-linux-gnueabihf libc-dev-armhf-cross \
gcc-aarch64-linux-gnu libc-dev-arm64-cross \
nodejs yarn zip --no-install-recommends || exit 1; \
rm -rf /var/lib/apt/lists/*;
# Cross compile setup
ENV OSX_SDK_VERSION 11.3
ENV OSX_SDK_DOWNLOAD_FILE=MacOSX${OSX_SDK_VERSION}.sdk.tar.xz
ENV OSX_SDK_DOWNLOAD_URL=https://github.com/phracker/MacOSX-SDKs/releases/download/${OSX_SDK_VERSION}/${OSX_SDK_DOWNLOAD_FILE}
ENV OSX_SDK_SHA=cd4f08a75577145b8f05245a2975f7c81401d75e9535dcffbb879ee1deefcbf4
ENV OSX_SDK MacOSX$OSX_SDK_VERSION.sdk
ENV OSX_NDK_X86 /usr/local/osx-ndk-x86
RUN wget ${OSX_SDK_DOWNLOAD_URL}
RUN echo "$OSX_SDK_SHA $OSX_SDK_DOWNLOAD_FILE" | sha256sum -c - || exit 1; \
git clone https://github.com/tpoechtrager/osxcross.git; \
mv $OSX_SDK_DOWNLOAD_FILE osxcross/tarballs/
RUN UNATTENDED=yes SDK_VERSION=${OSX_SDK_VERSION} OSX_VERSION_MIN=10.10 osxcross/build.sh || exit 1;
RUN cp osxcross/target/lib/* /usr/lib/ ; \
mv osxcross/target $OSX_NDK_X86; \
rm -rf osxcross;
ENV PATH $OSX_NDK_X86/bin:$PATH
RUN mkdir -p /root/.ssh; \
chmod 0700 /root/.ssh; \
ssh-keyscan github.com > /root/.ssh/known_hosts;
# Notes for self:
# To test locally:
# make generate
# make ui
# cd docker/compiler
# docker build . -t ghcr.io/stashapp/compiler:latest
# docker run --rm -v /PATH_TO_STASH:/stash -w /stash -i -t ghcr.io/stashapp/compiler:latest make build-cc-all
# # binaries will show up in /dist
# make build
# docker run -it -v /PATH_TO_STASH:/go/stash stashapp/compiler:latest /bin/bash
# cd stash
# make cross-compile-all
# # binaries will show up in /dist
# Windows:
# GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go build -ldflags "-extldflags '-static'" -tags extended
# Darwin
# CC=o64-clang CXX=o64-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build -tags extended
# env goreleaser --config=goreleaser-extended.yml --skip-publish --skip-validate --rm-dist --release-notes=temp/0.48-relnotes-ready.md

View File

@@ -1,22 +1,16 @@
host=ghcr.io
user=stashapp
repo=compiler
version=13
VERSION_IMAGE = ${host}/${user}/${repo}:${version}
LATEST_IMAGE = ${host}/${user}/${repo}:latest
version=7
latest:
docker build -t ${LATEST_IMAGE} .
docker build -t ${user}/${repo}:latest .
build:
docker build -t ${VERSION_IMAGE} -t ${LATEST_IMAGE} .
docker build -t ${user}/${repo}:${version} -t ${user}/${repo}:latest .
build-no-cache:
docker build --no-cache -t ${VERSION_IMAGE} -t ${LATEST_IMAGE} .
docker build --no-cache -t ${user}/${repo}:${version} -t ${user}/${repo}:latest .
# requires docker login ghcr.io
# echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin
push:
docker push ${VERSION_IMAGE}
docker push ${LATEST_IMAGE}
install: build
docker push ${user}/${repo}:${version}
docker push ${user}/${repo}:latest

View File

@@ -1,3 +1,3 @@
Modified from https://github.com/bep/dockerfiles/tree/master/ci-goreleaser
When the Dockerfile is changed, the version number should be incremented in [.github/workflows/build-compiler.yml](../../.github/workflows/build-compiler.yml) and the workflow [manually ran](). `env: COMPILER_IMAGE` in [.github/workflows/build.yml](../../.github/workflows/build.yml) also needs to be updated to pull the correct image tag.
When the dockerfile is changed, the version number should be incremented in the Makefile and the new version tag should be pushed to docker hub. The `scripts/cross-compile.sh` script should also be updated to use the new version number tag, and the github workflow files need to be updated to pull the correct image tag.

View File

@@ -1,27 +1,25 @@
# Docker Installation (for most 64-bit GNU/Linux systems)
StashApp is supported on most systems that support Docker. Your OS likely ships with or makes available the necessary packages.
StashApp is supported on most systems that support Docker and docker-compose. Your OS likely ships with or makes available the necessary packages.
## Dependencies
Only `docker` is required. For the most part your understanding of the technologies can be superficial. So long as you can follow commands and are open to reading a bit, you should be fine.
Only `docker` and `docker-compose` are required. For the most part your understanding of the technologies can be superficial. So long as you can follow commands and are open to reading a bit, you should be fine.
Installation instructions are available below, and if your distributions's repository ships a current version of docker, you may use that.
Installation instructions are available below, and if your distrobution's repository ships a current version of docker, you may use that.
https://docs.docker.com/engine/install/
On some distributions, `docker compose` is shipped seperately, usually as `docker-cli-compose`. docker-compose is not recommended.
### Get the docker-compose.yml file
Now you can either navigate to the [docker-compose.yml](https://raw.githubusercontent.com/stashapp/stash/develop/docker/production/docker-compose.yml) in the repository, or if you have curl, you can make your Linux console do it for you:
Now you can either navigate to the [docker-compose.yml](https://raw.githubusercontent.com/stashapp/stash/master/docker/production/docker-compose.yml) in the repository, or if you have curl, you can make your Linux console do it for you:
```
mkdir stashapp && cd stashapp
curl -o docker-compose.yml https://raw.githubusercontent.com/stashapp/stash/develop/docker/production/docker-compose.yml
curl -o docker-compose.yml https://raw.githubusercontent.com/stashapp/stash/master/docker/production/docker-compose.yml
```
Once you have that file where you want it, modify the settings as you please, and then run:
```
docker compose up -d
docker-compose up -d
```
Installing StashApp this way will by default bind stash to port 9999. This is available in your web browser locally at http://localhost:9999 or on your network as http://YOUR-LOCAL-IP:9999
@@ -31,9 +29,9 @@ Good luck and have fun!
### Docker
Docker is effectively a cross-platform software package repository. It allows you to ship an entire environment in what's referred to as a container. Containers are intended to hold everything that is needed to run an application from one place to another, making it easy for everyone along the way to reproduce the environment.
The StashApp docker container ships with everything you need to automatically run stash, including ffmpeg.
The StashApp docker container ships with everything you need to automatically build and run stash, including ffmpeg.
### docker compose
Docker Compose lets you specify how and where to run your containers, and to manage their environment. The docker-compose.yml file in this folder gets you a fully working instance of StashApp exactly as you would need it to have a reasonable instance for testing / developing on. If you are deploying a live instance for production, a [reverse proxy](https://docs.stashapp.cc/guides/reverse-proxy/) (such as NGINX or Traefik) is recommended, but not required.
### docker-compose
Docker Compose lets you specify how and where to run your containers, and to manage their environment. The docker-compose.yml file in this folder gets you a fully working instance of StashApp exactly as you would need it to have a reasonable instance for testing / developing on. If you are deploying a live instance for production, a reverse proxy (such as NGINX or Traefik) is recommended, but not required.
The latest version is always recommended.

View File

@@ -1,5 +1,6 @@
# APPNICENAME=Stash
# APPDESCRIPTION=An organizer for your porn, written in Go
version: '3.4'
services:
stash:
image: stashapp/stash:latest
@@ -26,12 +27,10 @@ services:
- /etc/localtime:/etc/localtime:ro
## Adjust below paths (the left part) to your liking.
## E.g. you can change ./config:/root/.stash to ./stash:/root/.stash
## The left part is the path on your host, the right part is the path in the stash container.
## Keep configs, scrapers, and plugins here.
- ./config:/root/.stash
## Point this at your collection.
## The left side is where your collection is on your host, the right side is where it will be in stash.
- ./data:/data
## This is where your stash's metadata lives
- ./metadata:/metadata

View File

@@ -1,24 +1,3 @@
## Goals and design vision
The goal of stash is to be:
- an application for organising and viewing adult content - currently this is videos and images, in future this will be extended to include audio and text content
- organising includes scraping of metadata from websites and metadata repositories
- free and open-source
- portable and offline - can be run on a USB stick without needing to install dependencies (with the exception of ffmpeg)
- minimal, but highly extensible. The core feature set should be the minimum required to achieve the primary goal, while being extensible enough to extend via plugins
- easy to learn and use, with minimal technical knowledge required
The core stash system is not intended for:
- managing downloading of content
- managing content on external websites
- publically sharing content
Other requirements:
- support as many video and image formats as possible
- interfaces with external systems (for example stash-box) should be made as generic as possible.
Design considerations:
- features are easy to add and difficult to remove. Large superfluous features should be scrutinised and avoided where possible (eg DLNA, filename parser). Such features should be considered for third-party plugins instead.
## Technical Debt
Please be sure to consider how heavily your contribution impacts the maintainability of the project long term, sometimes less is more. We don't want to merge collossal pull requests with hundreds of dependencies by a driveby contributor.

View File

@@ -4,9 +4,8 @@
* [Go](https://golang.org/dl/)
* [GolangCI](https://golangci-lint.run/) - A meta-linter which runs several linters in parallel
* To install, follow the [local installation instructions](https://golangci-lint.run/welcome/install/#local-installation)
* [nodejs](https://nodejs.org/en/download) - nodejs runtime
* corepack/[pnpm](https://pnpm.io/installation) - nodejs package manager (included with nodejs)
* To install, follow the [local installation instructions](https://golangci-lint.run/usage/install/#local-installation)
* [Yarn](https://yarnpkg.com/en/docs/install) - Yarn package manager
## Environment
@@ -23,24 +22,19 @@ NOTE: The `make` command in Windows will be `mingw32-make` with MinGW. For examp
### macOS
1. If you don't have it already, install the [Homebrew package manager](https://brew.sh).
2. Install dependencies: `brew install go git gcc make node ffmpeg`
2. Install dependencies: `brew install go git yarn gcc make node ffmpeg`
### Linux
#### Arch Linux
1. Install dependencies: `sudo pacman -S go git gcc make nodejs ffmpeg --needed`
1. Install dependencies: `sudo pacman -S go git yarn gcc make nodejs ffmpeg --needed`
#### Ubuntu
1. Install dependencies: `sudo apt-get install golang git gcc nodejs ffmpeg -y`
### OpenBSD
1. Install dependencies `doas pkg_add gmake go git node cmake ffmpeg`
2. Follow the instructions below to build a release, but replace the final step `make build-release` with `gmake flags-release stash`, to [avoid the PIE buildmode](https://github.com/golang/go/issues/59866).
NOTE: The `make` command in OpenBSD will be `gmake`. For example, `make pre-ui` will be `gmake pre-ui`.
2. Enable corepack in Node.js: `corepack enable`
3. Install yarn: `corepack prepare yarn@stable --activate`
## Commands
@@ -49,10 +43,11 @@ NOTE: The `make` command in OpenBSD will be `gmake`. For example, `make pre-ui`
* `make generate-stash-box-client` - Generate Go files for the Stash-box client code.
* `make ui` - Builds the UI. Requires `make pre-ui` to have been run.
* `make stash` - Builds the `stash` binary (make sure to build the UI as well... see below)
* `make stash-macapp` - Builds the `Stash.app` macOS app (only works when on macOS, for cross-compilation see below)
* `make stash-release` - Builds a release version the `stash` binary, with debug information removed
* `make phasher` - Builds the `phasher` binary
* `make build` - Builds both the `stash` and `phasher` binaries, alias for `make stash phasher`
* `make build-release` - Builds release versions (debug information removed) of both the `stash` and `phasher` binaries, alias for `make flags-release flags-pie build`
* `make phasher-release` - Builds a release version the `phasher` binary, with debug information removed
* `make build` - Builds both the `stash` and `phasher` binaries
* `make build-release` - Builds release versions of both the `stash` and `phasher` binaries
* `make docker-build` - Locally builds and tags a complete 'stash/build' docker image
* `make docker-cuda-build` - Locally builds and tags a complete 'stash/cuda-build' docker image
* `make validate` - Runs all of the tests and checks required to submit a PR
@@ -60,20 +55,9 @@ NOTE: The `make` command in OpenBSD will be `gmake`. For example, `make pre-ui`
* `make it` - Runs all unit and integration tests
* `make fmt` - Formats the Go source code
* `make fmt-ui` - Formats the UI source code
* `make validate-ui` - Runs tests and checks for the UI only
* `make fmt-ui-quick` - (experimental) Formats only changed UI source code
* `make validate-ui-quick` - (experimental) Runs tests and checks of changed UI code
* `make server-start` - Runs a development stash server in the `.local` directory
* `make server-clean` - Removes the `.local` directory and all of its contents
* `make ui-start` - Runs the UI in development mode. Requires a running Stash server to connect to - the server URL can be changed from the default of `http://localhost:9999` using the environment variable `VITE_APP_PLATFORM_URL`, but keep in mind that authentication cannot be used since the session authorization cookie cannot be sent cross-origin. The UI runs on port `3000` or the next available port.
When building, you can optionally prepend `flags-*` targets to the target list in your `make` command to use different build flags:
* `flags-release` (e.g. `make flags-release stash`) - Remove debug information from the binary.
* `flags-pie` (e.g. `make flags-pie build`) - Build a PIE (Position Independent Executable) binary. This provides increased security, but it is unsupported on some systems (notably 32-bit ARM and OpenBSD).
* `flags-static` (e.g. `make flags-static phasher`) - Build a statically linked binary (the default is a dynamically linked binary).
* `flags-static-pie` (e.g. `make flags-static-pie stash`) - Build a statically linked PIE binary (using `flags-static` and `flags-pie` separately will not work).
* `flags-static-windows` (e.g. `make flags-static-windows build`) - Identical to `flags-static-pie`, but does not enable the `netgo` build tag, which is not needed for static builds on Windows.
* `make ui-start` - Runs the UI in development mode. Requires a running Stash server to connect to. The server port can be changed from the default of `9999` using the environment variable `VITE_APP_PLATFORM_PORT`. The UI runs on port `3000` or the next available port.
## Local development quickstart
@@ -111,19 +95,13 @@ Simply run `make` or `make release`, or equivalently:
3. Run `make ui` to build the frontend
4. Run `make build-release` to build a release executable for your current platform
## Cross-compiling
## Cross compiling
This project uses a modification of the [CI-GoReleaser](https://github.com/bep/dockerfiles/tree/master/ci-goreleaser) Docker container for cross-compilation, defined in `docker/compiler/Dockerfile`.
This project uses a modification of the [CI-GoReleaser](https://github.com/bep/dockerfiles/tree/master/ci-goreleaser) docker container to create an environment
where the app can be cross-compiled. This process is kicked off by CI via the `scripts/cross-compile.sh` script. Run the following
command to open a bash shell to the container to poke around:
To cross-compile the app yourself:
1. Run `make pre-ui`, `make generate` and `make ui` outside the container, to generate files and build the UI.
2. Pull the latest compiler image from GHCR: `docker pull ghcr.io/stashapp/compiler`
3. Run `docker run --rm --mount type=bind,source="$(pwd)",target=/stash -w /stash -it ghcr.io/stashapp/compiler /bin/bash` to open a shell inside the container.
4. From inside the container, run `make build-cc-all` to build for all platforms, or run `make build-cc-{platform}` to build for a specific platform (have a look at the `Makefile` for the list of targets).
5. You will find the compiled binaries in `dist/`.
NOTE: Since the container is run as UID 0 (root), the resulting binaries (and the `dist/` folder itself, if it had to be created) will be owned by root.
`docker run --rm --mount type=bind,source="$(pwd)",target=/stash -w /stash -i -t stashapp/compiler:latest /bin/bash`
## Profiling

176
go.mod
View File

@@ -1,129 +1,115 @@
module github.com/stashapp/stash
go 1.24.3
require (
github.com/99designs/gqlgen v0.17.73
github.com/WithoutPants/sortorder v0.0.0-20230616003020-921c9ef69552
github.com/Yamashou/gqlgenc v0.32.1
github.com/99designs/gqlgen v0.17.2
github.com/Yamashou/gqlgenc v0.0.6
github.com/anacrolix/dms v1.2.2
github.com/antchfx/htmlquery v1.3.5
github.com/asticode/go-astisub v0.25.1
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d
github.com/chromedp/chromedp v0.14.2
github.com/corona10/goimagehash v1.1.0
github.com/disintegration/imaging v1.6.2
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
github.com/doug-martin/goqu/v9 v9.18.0
github.com/go-chi/chi/v5 v5.2.2
github.com/go-chi/cors v1.2.1
github.com/go-chi/httplog v0.3.1
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
github.com/golang-jwt/jwt/v4 v4.5.2
github.com/golang-migrate/migrate/v4 v4.16.2
github.com/google/uuid v1.6.0
github.com/antchfx/htmlquery v1.2.5-0.20211125074323-810ee8082758
github.com/chromedp/cdproto v0.0.0-20210622022015-fe1827b46b84
github.com/chromedp/chromedp v0.7.3
github.com/corona10/goimagehash v1.0.3
github.com/disintegration/imaging v1.6.0
github.com/go-chi/chi v4.0.2+incompatible
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang-jwt/jwt/v4 v4.0.0
github.com/golang-migrate/migrate/v4 v4.15.0-beta.1
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.1
github.com/gorilla/sessions v1.2.0
github.com/gorilla/websocket v1.5.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/hasura/go-graphql-client v0.13.1
github.com/jinzhu/copier v0.4.0
github.com/jmoiron/sqlx v1.4.0
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/jmoiron/sqlx v1.3.1
github.com/json-iterator/go v1.1.12
github.com/kermieisinthehouse/gosx-notifier v0.1.2
github.com/kermieisinthehouse/systray v1.2.4
github.com/knadh/koanf/parsers/yaml v1.1.0
github.com/knadh/koanf/providers/env v1.1.0
github.com/knadh/koanf/providers/file v1.2.0
github.com/knadh/koanf/providers/posflag v1.0.1
github.com/knadh/koanf/v2 v2.2.1
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-sqlite3 v1.14.22
github.com/mitchellh/mapstructure v1.5.0
github.com/mattn/go-sqlite3 v1.14.7
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cast v1.6.0
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.16.0
github.com/vearutop/statigz v1.4.0
github.com/vektah/dataloaden v0.3.0
github.com/vektah/gqlparser/v2 v2.5.27
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f
github.com/sirupsen/logrus v1.8.1
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.10.1
github.com/stretchr/testify v1.7.1
github.com/tidwall/gjson v1.9.3
github.com/tidwall/pretty v1.2.0 // indirect
github.com/vektra/mockery/v2 v2.10.0
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e
github.com/zencoder/go-dash/v3 v3.0.2
golang.org/x/crypto v0.45.0
golang.org/x/image v0.18.0
golang.org/x/net v0.47.0
golang.org/x/sys v0.38.0
golang.org/x/term v0.37.0
golang.org/x/text v0.31.0
golang.org/x/time v0.10.0
gopkg.in/guregu/null.v4 v4.0.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
golang.org/x/image v0.5.0
golang.org/x/net v0.7.0
golang.org/x/sys v0.5.0
golang.org/x/term v0.5.0
golang.org/x/text v0.7.0
golang.org/x/tools v0.1.12 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/antchfx/xpath v1.3.5 // indirect
github.com/WithoutPants/sortorder v0.0.0-20230616003020-921c9ef69552
github.com/asticode/go-astisub v0.20.0
github.com/doug-martin/goqu/v9 v9.18.0
github.com/go-chi/cors v1.2.1
github.com/go-chi/httplog v0.2.1
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
github.com/hashicorp/golang-lru v0.5.4
github.com/kermieisinthehouse/gosx-notifier v0.1.1
github.com/kermieisinthehouse/systray v1.2.4
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/spf13/cast v1.4.1
github.com/vearutop/statigz v1.1.6
github.com/vektah/dataloaden v0.3.0
github.com/vektah/gqlparser/v2 v2.4.2
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e
github.com/zencoder/go-dash/v3 v3.0.2
gopkg.in/guregu/null.v4 v4.0.0
)
require (
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/antchfx/xpath v1.2.0 // indirect
github.com/asticode/go-astikit v0.20.0 // indirect
github.com/asticode/go-astits v1.8.0 // indirect
github.com/chromedp/sysutil v1.1.0 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/chromedp/sysutil v1.0.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-chi/chi/v5 v5.0.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/gobwas/ws v1.1.0-rc.5 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matryer/moq v0.2.3 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/zerolog v1.30.0 // indirect
github.com/rs/zerolog v1.26.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sosodev/duration v1.3.1 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/cobra v1.4.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.16.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/stretchr/objx v0.2.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/urfave/cli/v2 v2.27.6 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.3 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/tools v0.38.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
github.com/urfave/cli/v2 v2.8.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.uber.org/atomic v1.7.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999
go 1.19

749
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -7,48 +7,32 @@ exec:
filename: internal/api/generated_exec.go
model:
filename: internal/api/generated_models.go
resolver:
filename: internal/api/resolver.go
type: Resolver
struct_tag: gqlgen
autobind:
- github.com/stashapp/stash/internal/api
- github.com/stashapp/stash/pkg/models
- github.com/stashapp/stash/pkg/plugin
- github.com/stashapp/stash/pkg/scraper
- github.com/stashapp/stash/internal/identify
- github.com/stashapp/stash/internal/dlna
- github.com/stashapp/stash/pkg/stashbox
- github.com/stashapp/stash/pkg/scraper/stashbox
models:
# Scalars
ID:
model:
- github.com/99designs/gqlgen/graphql.ID
- github.com/99designs/gqlgen/graphql.IntID
- github.com/stashapp/stash/pkg/models.FileID
- github.com/stashapp/stash/pkg/models.FolderID
Int64:
model: github.com/99designs/gqlgen/graphql.Int64
Timestamp:
model: github.com/stashapp/stash/internal/api.Timestamp
BoolMap:
model: github.com/stashapp/stash/internal/api.BoolMap
PluginConfigMap:
model: github.com/stashapp/stash/internal/api.PluginConfigMap
File:
model: github.com/stashapp/stash/internal/api.File
VideoFile:
# define to force resolvers
Image:
model: github.com/stashapp/stash/pkg/models.Image
fields:
# override float fields - #1572
duration:
fieldName: DurationFinite
frame_rate:
fieldName: FrameRateFinite
# movie is group under the hood
Movie:
model: github.com/stashapp/stash/pkg/models.Group
MovieFilterType:
model: github.com/stashapp/stash/pkg/models.GroupFilterType
title:
resolver: true
# autobind on config causes generation issues
BlobsStorageType:
model: github.com/stashapp/stash/internal/manager/config.BlobsStorageType
@@ -68,8 +52,6 @@ models:
model: github.com/stashapp/stash/internal/manager/config.ConfigDisableDropdownCreate
ScanMetadataOptions:
model: github.com/stashapp/stash/internal/manager/config.ScanMetadataOptions
CleanGeneratedInput:
model: github.com/stashapp/stash/internal/manager/task.CleanGeneratedOptions
AutoTagMetadataOptions:
model: github.com/stashapp/stash/internal/manager/config.AutoTagMetadataOptions
SystemStatus:
@@ -125,6 +107,9 @@ models:
model: github.com/stashapp/stash/internal/identify.FieldStrategy
ScraperSource:
model: github.com/stashapp/stash/pkg/scraper.Source
# rebind inputs to types
StashIDInput:
model: github.com/stashapp/stash/pkg/models.StashID
IdentifySourceInput:
model: github.com/stashapp/stash/internal/identify.Source
IdentifyFieldOptionsInput:
@@ -133,15 +118,4 @@ models:
model: github.com/stashapp/stash/internal/identify.MetadataOptions
ScraperSourceInput:
model: github.com/stashapp/stash/pkg/scraper.Source
SavedFindFilterType:
model: github.com/stashapp/stash/pkg/models.FindFilterType
# force resolvers
ConfigResult:
fields:
plugins:
resolver: true
Performer:
fields:
career_length:
resolver: true

View File

@@ -0,0 +1,215 @@
fragment ConfigGeneralData on ConfigGeneralResult {
stashes {
path
excludeVideo
excludeImage
}
databasePath
backupDirectoryPath
generatedPath
metadataPath
scrapersPath
cachePath
blobsPath
blobsStorage
calculateMD5
videoFileNamingAlgorithm
parallelTasks
previewAudio
previewSegments
previewSegmentDuration
previewExcludeStart
previewExcludeEnd
previewPreset
transcodeHardwareAcceleration
maxTranscodeSize
maxStreamingTranscodeSize
writeImageThumbnails
createImageClipsFromVideos
apiKey
username
password
maxSessionAge
logFile
logOut
logLevel
logAccess
createGalleriesFromFolders
galleryCoverRegex
videoExtensions
imageExtensions
galleryExtensions
excludes
imageExcludes
customPerformerImageLocation
scraperUserAgent
scraperCertCheck
scraperCDPPath
stashBoxes {
name
endpoint
api_key
}
pythonPath
transcodeInputArgs
transcodeOutputArgs
liveTranscodeInputArgs
liveTranscodeOutputArgs
drawFunscriptHeatmapRange
}
fragment ConfigInterfaceData on ConfigInterfaceResult {
menuItems
soundOnPreview
wallShowTitle
wallPlayback
showScrubber
maximumLoopDuration
noBrowser
notificationsEnabled
autostartVideo
autostartVideoOnPlaySelected
continuePlaylistDefault
showStudioAsText
css
cssEnabled
javascript
javascriptEnabled
customLocales
customLocalesEnabled
language
imageLightbox {
slideshowDelay
displayMode
scaleUp
resetZoomOnNav
scrollMode
scrollAttemptsBeforeChange
}
disableDropdownCreate {
performer
tag
studio
movie
}
handyKey
funscriptOffset
useStashHostedFunscript
}
fragment ConfigDLNAData on ConfigDLNAResult {
serverName
enabled
whitelistedIPs
interfaces
videoSortOrder
}
fragment ConfigScrapingData on ConfigScrapingResult {
scraperUserAgent
scraperCertCheck
scraperCDPPath
excludeTagPatterns
}
fragment IdentifyFieldOptionsData on IdentifyFieldOptions {
field
strategy
createMissing
}
fragment IdentifyMetadataOptionsData on IdentifyMetadataOptions {
fieldOptions {
...IdentifyFieldOptionsData
}
setCoverImage
setOrganized
includeMalePerformers
skipMultipleMatches
skipMultipleMatchTag
skipSingleNamePerformers
skipSingleNamePerformerTag
}
fragment ScraperSourceData on ScraperSource {
stash_box_index
stash_box_endpoint
scraper_id
}
fragment ConfigDefaultSettingsData on ConfigDefaultSettingsResult {
scan {
useFileMetadata
stripFileExtension
scanGenerateCovers
scanGeneratePreviews
scanGenerateImagePreviews
scanGenerateSprites
scanGeneratePhashes
scanGenerateThumbnails
scanGenerateClipPreviews
}
identify {
sources {
source {
...ScraperSourceData
}
options {
...IdentifyMetadataOptionsData
}
}
options {
...IdentifyMetadataOptionsData
}
}
autoTag {
performers
studios
tags
}
generate {
covers
sprites
previews
imagePreviews
previewOptions {
previewSegments
previewSegmentDuration
previewExcludeStart
previewExcludeEnd
previewPreset
}
markers
markerImagePreviews
markerScreenshots
transcodes
phashes
interactiveHeatmapsSpeeds
clipPreviews
}
deleteFile
deleteGenerated
}
fragment ConfigData on ConfigResult {
general {
...ConfigGeneralData
}
interface {
...ConfigInterfaceData
}
dlna {
...ConfigDLNAData
}
scraping {
...ConfigScrapingData
}
defaults {
...ConfigDefaultSettingsData
}
ui
}

View File

@@ -0,0 +1,88 @@
fragment FolderData on Folder {
id
path
}
fragment VideoFileData on VideoFile {
id
path
size
mod_time
duration
video_codec
audio_codec
width
height
frame_rate
bit_rate
fingerprints {
type
value
}
}
fragment ImageFileData on ImageFile {
id
path
size
mod_time
width
height
fingerprints {
type
value
}
}
fragment GalleryFileData on GalleryFile {
id
path
size
mod_time
fingerprints {
type
value
}
}
fragment VisualFileData on VisualFile {
... on BaseFile {
id
path
size
mod_time
fingerprints {
type
value
}
}
... on ImageFile {
id
path
size
mod_time
width
height
fingerprints {
type
value
}
}
... on VideoFile {
id
path
size
mod_time
duration
video_codec
audio_codec
width
height
frame_rate
bit_rate
fingerprints {
type
value
}
}
}

View File

@@ -0,0 +1,6 @@
fragment SavedFilterData on SavedFilter {
id
mode
name
filter
}

View File

@@ -1,11 +1,9 @@
fragment SlimGalleryData on Gallery {
id
title
code
date
urls
url
details
photographer
rating100
organized
files {
@@ -15,6 +13,15 @@ fragment SlimGalleryData on Gallery {
...FolderData
}
image_count
cover {
id
files {
...ImageFileData
}
paths {
thumbnail
}
}
chapters {
id
title
@@ -39,8 +46,4 @@ fragment SlimGalleryData on Gallery {
scenes {
...SlimSceneData
}
paths {
cover
preview
}
}

View File

@@ -0,0 +1,38 @@
fragment GalleryData on Gallery {
id
created_at
updated_at
title
date
url
details
rating100
organized
files {
...GalleryFileData
}
folder {
...FolderData
}
chapters {
...GalleryChapterData
}
cover {
...SlimImageData
}
studio {
...SlimStudioData
}
tags {
...SlimTagData
}
performers {
...PerformerData
}
scenes {
...SlimSceneData
}
}

View File

@@ -1,15 +1,16 @@
fragment SlimImageData on Image {
id
title
code
date
urls
details
photographer
url
rating100
organized
o_counter
files {
...ImageFileData
}
paths {
thumbnail
preview

View File

@@ -0,0 +1,41 @@
fragment ImageData on Image {
id
title
rating100
date
url
organized
o_counter
created_at
updated_at
files {
...ImageFileData
}
paths {
thumbnail
preview
image
}
galleries {
...GalleryData
}
studio {
...SlimStudioData
}
tags {
...SlimTagData
}
performers {
...PerformerData
}
visual_files {
...VisualFileData
}
}

View File

@@ -7,5 +7,4 @@ fragment JobData on Job {
startTime
endTime
addTime
error
}

View File

@@ -0,0 +1,6 @@
fragment SlimMovieData on Movie {
id
name
front_image_path
rating100
}

View File

@@ -0,0 +1,25 @@
fragment MovieData on Movie {
id
name
aliases
duration
date
rating100
director
studio {
...SlimStudioData
}
synopsis
url
front_image_path
back_image_path
scene_count
scenes {
id
title
path
}
}

View File

@@ -3,7 +3,9 @@ fragment SlimPerformerData on Performer {
name
disambiguation
gender
urls
url
twitter
instagram
image_path
favorite
ignore_auto_tag
@@ -16,8 +18,7 @@ fragment SlimPerformerData on Performer {
fake_tits
penis_length
circumcised
career_start
career_end
career_length
tattoos
piercings
alias_list
@@ -28,7 +29,6 @@ fragment SlimPerformerData on Performer {
stash_ids {
endpoint
stash_id
updated_at
}
rating100
death_date
@@ -40,7 +40,4 @@ fragment SelectPerformerData on Performer {
name
disambiguation
alias_list
image_path
birthdate
death_date
}

View File

@@ -0,0 +1,46 @@
fragment PerformerData on Performer {
id
checksum
name
disambiguation
url
gender
twitter
instagram
birthdate
ethnicity
country
eye_color
height_cm
measurements
fake_tits
penis_length
circumcised
career_length
tattoos
piercings
alias_list
favorite
ignore_auto_tag
image_path
scene_count
image_count
gallery_count
movie_count
performer_count
o_counter
tags {
...SlimTagData
}
stash_ids {
stash_id
endpoint
}
rating100
details
death_date
hair_color
weight
}

View File

@@ -0,0 +1,24 @@
fragment SceneMarkerData on SceneMarker {
id
title
seconds
stream
preview
screenshot
scene {
id
}
primary_tag {
id
name
aliases
}
tags {
id
name
aliases
}
}

View File

@@ -58,8 +58,8 @@ fragment SlimSceneData on Scene {
image_path
}
groups {
group {
movies {
movie {
id
name
front_image_path
@@ -75,7 +75,6 @@ fragment SlimSceneData on Scene {
performers {
id
name
disambiguation
gender
favorite
image_path
@@ -84,6 +83,5 @@ fragment SlimSceneData on Scene {
stash_ids {
endpoint
stash_id
updated_at
}
}

View File

@@ -0,0 +1,78 @@
fragment SceneData on Scene {
id
title
code
details
director
urls
date
rating100
o_counter
organized
interactive
interactive_speed
captions {
language_code
caption_type
}
created_at
updated_at
resume_time
last_played_at
play_duration
play_count
files {
...VideoFileData
}
paths {
screenshot
preview
stream
webp
vtt
sprite
funscript
interactive_heatmap
caption
}
scene_markers {
...SceneMarkerData
}
galleries {
...SlimGalleryData
}
studio {
...SlimStudioData
}
movies {
movie {
...MovieData
}
scene_index
}
tags {
...SlimTagData
}
performers {
...PerformerData
}
stash_ids {
endpoint
stash_id
}
sceneStreams {
url
mime_type
label
}
}

View File

@@ -0,0 +1,248 @@
fragment ScrapedStudioData on ScrapedStudio {
stored_id
name
url
parent {
stored_id
name
url
image
remote_site_id
}
image
remote_site_id
}
fragment ScrapedPerformerData on ScrapedPerformer {
stored_id
name
disambiguation
gender
url
twitter
instagram
birthdate
ethnicity
country
eye_color
height
measurements
fake_tits
penis_length
circumcised
career_length
tattoos
piercings
aliases
tags {
...ScrapedSceneTagData
}
images
details
death_date
hair_color
weight
remote_site_id
}
fragment ScrapedScenePerformerData on ScrapedPerformer {
stored_id
name
disambiguation
gender
url
twitter
instagram
birthdate
ethnicity
country
eye_color
height
measurements
fake_tits
penis_length
circumcised
career_length
tattoos
piercings
aliases
tags {
...ScrapedSceneTagData
}
remote_site_id
images
details
death_date
hair_color
weight
}
fragment ScrapedMovieStudioData on ScrapedStudio {
stored_id
name
url
}
fragment ScrapedMovieData on ScrapedMovie {
name
aliases
duration
date
rating
director
url
synopsis
front_image
back_image
studio {
...ScrapedMovieStudioData
}
}
fragment ScrapedSceneMovieData on ScrapedMovie {
stored_id
name
aliases
duration
date
rating
director
url
synopsis
}
fragment ScrapedSceneStudioData on ScrapedStudio {
stored_id
name
url
parent {
stored_id
name
url
image
remote_site_id
}
image
remote_site_id
}
fragment ScrapedSceneTagData on ScrapedTag {
stored_id
name
}
fragment ScrapedSceneData on ScrapedScene {
title
code
details
director
urls
date
image
remote_site_id
file {
size
duration
video_codec
audio_codec
width
height
framerate
bitrate
}
studio {
...ScrapedSceneStudioData
}
tags {
...ScrapedSceneTagData
}
performers {
...ScrapedScenePerformerData
}
movies {
...ScrapedSceneMovieData
}
fingerprints {
hash
algorithm
duration
}
}
fragment ScrapedGalleryData on ScrapedGallery {
title
details
url
date
studio {
...ScrapedSceneStudioData
}
tags {
...ScrapedSceneTagData
}
performers {
...ScrapedScenePerformerData
}
}
fragment ScrapedStashBoxSceneData on ScrapedScene {
title
code
details
director
url
date
image
remote_site_id
duration
file {
size
duration
video_codec
audio_codec
width
height
framerate
bitrate
}
fingerprints {
hash
algorithm
duration
}
studio {
...ScrapedSceneStudioData
}
tags {
...ScrapedSceneTagData
}
performers {
...ScrapedScenePerformerData
}
movies {
...ScrapedSceneMovieData
}
}
fragment ScrapedStashBoxPerformerData on StashBoxPerformerQueryResult {
query
results {
...ScrapedScenePerformerData
}
}

View File

@@ -0,0 +1,15 @@
fragment SlimStudioData on Studio {
id
name
image_path
stash_ids {
endpoint
stash_id
}
parent_studio {
id
}
details
rating100
aliases
}

View File

@@ -0,0 +1,35 @@
fragment StudioData on Studio {
id
name
url
parent_studio {
id
name
url
image_path
}
child_studios {
id
name
image_path
}
ignore_auto_tag
image_path
scene_count
scene_count_all: scene_count(depth: -1)
image_count
image_count_all: image_count(depth: -1)
gallery_count
gallery_count_all: gallery_count(depth: -1)
performer_count
performer_count_all: performer_count(depth: -1)
movie_count
movie_count_all: movie_count(depth: -1)
stash_ids {
stash_id
endpoint
}
details
rating100
aliases
}

View File

@@ -0,0 +1,6 @@
fragment SlimTagData on Tag {
id
name
aliases
image_path
}

View File

@@ -0,0 +1,26 @@
fragment TagData on Tag {
id
name
description
aliases
ignore_auto_tag
image_path
scene_count
scene_count_all: scene_count(depth: -1)
scene_marker_count
scene_marker_count_all: scene_marker_count(depth: -1)
image_count
image_count_all: image_count(depth: -1)
gallery_count
gallery_count_all: gallery_count(depth: -1)
performer_count
performer_count_all: performer_count(depth: -1)
parents {
...SlimTagData
}
children {
...SlimTagData
}
}

View File

@@ -0,0 +1,45 @@
mutation Setup($input: SetupInput!) {
setup(input: $input)
}
mutation Migrate($input: MigrateInput!) {
migrate(input: $input)
}
mutation ConfigureGeneral($input: ConfigGeneralInput!) {
configureGeneral(input: $input) {
...ConfigGeneralData
}
}
mutation ConfigureInterface($input: ConfigInterfaceInput!) {
configureInterface(input: $input) {
...ConfigInterfaceData
}
}
mutation ConfigureDLNA($input: ConfigDLNAInput!) {
configureDLNA(input: $input) {
...ConfigDLNAData
}
}
mutation ConfigureScraping($input: ConfigScrapingInput!) {
configureScraping(input: $input) {
...ConfigScrapingData
}
}
mutation ConfigureDefaults($input: ConfigDefaultSettingsInput!) {
configureDefaults(input: $input) {
...ConfigDefaultSettingsData
}
}
mutation ConfigureUI($input: Map!) {
configureUI(input: $input)
}
mutation GenerateAPIKey($input: GenerateAPIKeyInput!) {
generateAPIKey(input: $input)
}

View File

@@ -0,0 +1,3 @@
mutation DeleteFiles($ids: [ID!]!) {
deleteFiles(ids: $ids)
}

View File

@@ -0,0 +1,13 @@
mutation SaveFilter($input: SaveFilterInput!) {
saveFilter(input: $input) {
...SavedFilterData
}
}
mutation DestroySavedFilter($input: DestroyFilterInput!) {
destroySavedFilter(input: $input)
}
mutation SetDefaultFilter($input: SetDefaultFilterInput!) {
setDefaultFilter(input: $input)
}

View File

@@ -0,0 +1,45 @@
mutation GalleryCreate($input: GalleryCreateInput!) {
galleryCreate(input: $input) {
...GalleryData
}
}
mutation GalleryUpdate($input: GalleryUpdateInput!) {
galleryUpdate(input: $input) {
...GalleryData
}
}
mutation BulkGalleryUpdate($input: BulkGalleryUpdateInput!) {
bulkGalleryUpdate(input: $input) {
...GalleryData
}
}
mutation GalleriesUpdate($input: [GalleryUpdateInput!]!) {
galleriesUpdate(input: $input) {
...GalleryData
}
}
mutation GalleryDestroy(
$ids: [ID!]!
$delete_file: Boolean
$delete_generated: Boolean
) {
galleryDestroy(
input: {
ids: $ids
delete_file: $delete_file
delete_generated: $delete_generated
}
)
}
mutation AddGalleryImages($gallery_id: ID!, $image_ids: [ID!]!) {
addGalleryImages(input: { gallery_id: $gallery_id, image_ids: $image_ids })
}
mutation RemoveGalleryImages($gallery_id: ID!, $image_ids: [ID!]!) {
removeGalleryImages(input: { gallery_id: $gallery_id, image_ids: $image_ids })
}

View File

@@ -0,0 +1,51 @@
mutation MetadataImport {
metadataImport
}
mutation MetadataExport {
metadataExport
}
mutation ExportObjects($input: ExportObjectsInput!) {
exportObjects(input: $input)
}
mutation ImportObjects($input: ImportObjectsInput!) {
importObjects(input: $input)
}
mutation MetadataScan($input: ScanMetadataInput!) {
metadataScan(input: $input)
}
mutation MetadataGenerate($input: GenerateMetadataInput!) {
metadataGenerate(input: $input)
}
mutation MetadataAutoTag($input: AutoTagMetadataInput!) {
metadataAutoTag(input: $input)
}
mutation MetadataIdentify($input: IdentifyMetadataInput!) {
metadataIdentify(input: $input)
}
mutation MetadataClean($input: CleanMetadataInput!) {
metadataClean(input: $input)
}
mutation MigrateHashNaming {
migrateHashNaming
}
mutation BackupDatabase($input: BackupDatabaseInput!) {
backupDatabase(input: $input)
}
mutation AnonymiseDatabase($input: AnonymiseDatabaseInput!) {
anonymiseDatabase(input: $input)
}
mutation OptimiseDatabase {
optimiseDatabase
}

View File

@@ -0,0 +1,25 @@
mutation MovieCreate($input: MovieCreateInput!) {
movieCreate(input: $input) {
...MovieData
}
}
mutation MovieUpdate($input: MovieUpdateInput!) {
movieUpdate(input: $input) {
...MovieData
}
}
mutation BulkMovieUpdate($input: BulkMovieUpdateInput!) {
bulkMovieUpdate(input: $input) {
...MovieData
}
}
mutation MovieDestroy($id: ID!) {
movieDestroy(input: { id: $id })
}
mutation MoviesDestroy($ids: [ID!]!) {
moviesDestroy(ids: $ids)
}

View File

@@ -0,0 +1,25 @@
mutation PerformerCreate($input: PerformerCreateInput!) {
performerCreate(input: $input) {
...PerformerData
}
}
mutation PerformerUpdate($input: PerformerUpdateInput!) {
performerUpdate(input: $input) {
...PerformerData
}
}
mutation BulkPerformerUpdate($input: BulkPerformerUpdateInput!) {
bulkPerformerUpdate(input: $input) {
...PerformerData
}
}
mutation PerformerDestroy($id: ID!) {
performerDestroy(input: { id: $id })
}
mutation PerformersDestroy($ids: [ID!]!) {
performersDestroy(ids: $ids)
}

View File

@@ -0,0 +1,11 @@
mutation ReloadPlugins {
reloadPlugins
}
mutation RunPluginTask(
$plugin_id: ID!
$task_name: String!
$args: [PluginArgInput!]
) {
runPluginTask(plugin_id: $plugin_id, task_name: $task_name, args: $args)
}

View File

@@ -0,0 +1,45 @@
mutation SceneMarkerCreate(
$title: String!
$seconds: Float!
$scene_id: ID!
$primary_tag_id: ID!
$tag_ids: [ID!] = []
) {
sceneMarkerCreate(
input: {
title: $title
seconds: $seconds
scene_id: $scene_id
primary_tag_id: $primary_tag_id
tag_ids: $tag_ids
}
) {
...SceneMarkerData
}
}
mutation SceneMarkerUpdate(
$id: ID!
$title: String!
$seconds: Float!
$scene_id: ID!
$primary_tag_id: ID!
$tag_ids: [ID!] = []
) {
sceneMarkerUpdate(
input: {
id: $id
title: $title
seconds: $seconds
scene_id: $scene_id
primary_tag_id: $primary_tag_id
tag_ids: $tag_ids
}
) {
...SceneMarkerData
}
}
mutation SceneMarkerDestroy($id: ID!) {
sceneMarkerDestroy(id: $id)
}

View File

@@ -0,0 +1,93 @@
mutation SceneCreate($input: SceneCreateInput!) {
sceneCreate(input: $input) {
...SceneData
}
}
mutation SceneUpdate($input: SceneUpdateInput!) {
sceneUpdate(input: $input) {
...SceneData
}
}
mutation BulkSceneUpdate($input: BulkSceneUpdateInput!) {
bulkSceneUpdate(input: $input) {
...SceneData
}
}
mutation ScenesUpdate($input: [SceneUpdateInput!]!) {
scenesUpdate(input: $input) {
...SceneData
}
}
mutation SceneSaveActivity(
$id: ID!
$resume_time: Float
$playDuration: Float
) {
sceneSaveActivity(
id: $id
resume_time: $resume_time
playDuration: $playDuration
)
}
mutation SceneIncrementPlayCount($id: ID!) {
sceneIncrementPlayCount(id: $id)
}
mutation SceneIncrementO($id: ID!) {
sceneIncrementO(id: $id)
}
mutation SceneDecrementO($id: ID!) {
sceneDecrementO(id: $id)
}
mutation SceneResetO($id: ID!) {
sceneResetO(id: $id)
}
mutation SceneDestroy(
$id: ID!
$delete_file: Boolean
$delete_generated: Boolean
) {
sceneDestroy(
input: {
id: $id
delete_file: $delete_file
delete_generated: $delete_generated
}
)
}
mutation ScenesDestroy(
$ids: [ID!]!
$delete_file: Boolean
$delete_generated: Boolean
) {
scenesDestroy(
input: {
ids: $ids
delete_file: $delete_file
delete_generated: $delete_generated
}
)
}
mutation SceneGenerateScreenshot($id: ID!, $at: Float) {
sceneGenerateScreenshot(id: $id, at: $at)
}
mutation SceneAssignFile($input: AssignSceneFileInput!) {
sceneAssignFile(input: $input)
}
mutation SceneMerge($input: SceneMergeInput!) {
sceneMerge(input: $input) {
id
}
}

View File

@@ -0,0 +1,3 @@
mutation ReloadScrapers {
reloadScrapers
}

View File

@@ -12,10 +12,6 @@ mutation StashBoxBatchStudioTag($input: StashBoxBatchTagInput!) {
stashBoxBatchStudioTag(input: $input)
}
mutation StashBoxBatchTagTag($input: StashBoxBatchTagInput!) {
stashBoxBatchTagTag(input: $input)
}
mutation SubmitStashBoxSceneDraft($input: StashBoxDraftSubmissionInput!) {
submitStashBoxSceneDraft(input: $input)
}

View File

@@ -0,0 +1,19 @@
mutation StudioCreate($input: StudioCreateInput!) {
studioCreate(input: $input) {
...StudioData
}
}
mutation StudioUpdate($input: StudioUpdateInput!) {
studioUpdate(input: $input) {
...StudioData
}
}
mutation StudioDestroy($id: ID!) {
studioDestroy(input: { id: $id })
}
mutation StudiosDestroy($ids: [ID!]!) {
studiosDestroy(ids: $ids)
}

View File

@@ -0,0 +1,25 @@
mutation TagCreate($input: TagCreateInput!) {
tagCreate(input: $input) {
...TagData
}
}
mutation TagDestroy($id: ID!) {
tagDestroy(input: { id: $id })
}
mutation TagsDestroy($ids: [ID!]!) {
tagsDestroy(ids: $ids)
}
mutation TagUpdate($input: TagUpdateInput!) {
tagUpdate(input: $input) {
...TagData
}
}
mutation TagsMerge($source: [ID!]!, $destination: ID!) {
tagsMerge(input: { source: $source, destination: $destination }) {
...TagData
}
}

View File

@@ -0,0 +1,17 @@
query FindSavedFilter($id: ID!) {
findSavedFilter(id: $id) {
...SavedFilterData
}
}
query FindSavedFilters($mode: FilterMode) {
findSavedFilters(mode: $mode) {
...SavedFilterData
}
}
query FindDefaultFilter($mode: FilterMode!) {
findDefaultFilter(mode: $mode) {
...SavedFilterData
}
}

View File

@@ -0,0 +1,17 @@
query FindGalleries(
$filter: FindFilterType
$gallery_filter: GalleryFilterType
) {
findGalleries(gallery_filter: $gallery_filter, filter: $filter) {
count
galleries {
...SlimGalleryData
}
}
}
query FindGallery($id: ID!) {
findGallery(id: $id) {
...GalleryData
}
}

View File

@@ -0,0 +1,24 @@
query FindImages(
$filter: FindFilterType
$image_filter: ImageFilterType
$image_ids: [Int!]
) {
findImages(
filter: $filter
image_filter: $image_filter
image_ids: $image_ids
) {
count
megapixels
filesize
images {
...SlimImageData
}
}
}
query FindImage($id: ID!, $checksum: String) {
findImage(id: $id, checksum: $checksum) {
...ImageData
}
}

View File

@@ -0,0 +1,71 @@
query MarkerStrings($q: String, $sort: String) {
markerStrings(q: $q, sort: $sort) {
id
count
title
}
}
query AllStudiosForFilter {
allStudios {
id
name
aliases
}
}
query AllMoviesForFilter {
allMovies {
id
name
}
}
query AllTagsForFilter {
allTags {
id
name
aliases
}
}
query Stats {
stats {
scene_count
scenes_size
scenes_duration
image_count
images_size
gallery_count
performer_count
studio_count
movie_count
tag_count
total_o_count
total_play_duration
total_play_count
scenes_played
}
}
query Logs {
logs {
...LogEntryData
}
}
query Version {
version {
version
hash
build_time
}
}
query LatestVersion {
latestversion {
version
shorthash
release_date
url
}
}

View File

@@ -0,0 +1,14 @@
query FindMovies($filter: FindFilterType, $movie_filter: MovieFilterType) {
findMovies(filter: $filter, movie_filter: $movie_filter) {
count
movies {
...MovieData
}
}
}
query FindMovie($id: ID!) {
findMovie(id: $id) {
...MovieData
}
}

View File

@@ -0,0 +1,39 @@
query FindPerformers(
$filter: FindFilterType
$performer_filter: PerformerFilterType
$performer_ids: [Int!]
) {
findPerformers(
filter: $filter
performer_filter: $performer_filter
performer_ids: $performer_ids
) {
count
performers {
...PerformerData
}
}
}
query FindPerformer($id: ID!) {
findPerformer(id: $id) {
...PerformerData
}
}
query FindPerformersForSelect(
$filter: FindFilterType
$performer_filter: PerformerFilterType
$performer_ids: [Int!]
) {
findPerformers(
filter: $filter
performer_filter: $performer_filter
performer_ids: $performer_ids
) {
count
performers {
...SelectPerformerData
}
}
}

View File

@@ -0,0 +1,31 @@
query Plugins {
plugins {
id
name
description
url
version
tasks {
name
description
}
hooks {
name
description
hooks
}
}
}
query PluginTasks {
pluginTasks {
name
description
plugin {
id
name
}
}
}

View File

@@ -0,0 +1,91 @@
query FindScenes(
$filter: FindFilterType
$scene_filter: SceneFilterType
$scene_ids: [Int!]
) {
findScenes(
filter: $filter
scene_filter: $scene_filter
scene_ids: $scene_ids
) {
count
filesize
duration
scenes {
...SlimSceneData
}
}
}
query FindScenesByPathRegex($filter: FindFilterType) {
findScenesByPathRegex(filter: $filter) {
count
filesize
duration
scenes {
...SlimSceneData
}
}
}
query FindDuplicateScenes($distance: Int, $duration_diff: Float) {
findDuplicateScenes(distance: $distance, duration_diff: $duration_diff) {
...SlimSceneData
}
}
query FindScene($id: ID!, $checksum: String) {
findScene(id: $id, checksum: $checksum) {
...SceneData
}
}
query FindSceneMarkerTags($id: ID!) {
sceneMarkerTags(scene_id: $id) {
tag {
id
name
}
scene_markers {
...SceneMarkerData
}
}
}
query ParseSceneFilenames(
$filter: FindFilterType!
$config: SceneParserInput!
) {
parseSceneFilenames(filter: $filter, config: $config) {
count
results {
scene {
...SlimSceneData
}
title
code
details
director
url
date
rating
studio_id
gallery_ids
movies {
movie_id
}
performer_ids
tag_ids
}
}
}
query SceneStreams($id: ID!) {
findScene(id: $id) {
sceneStreams {
url
mime_type
label
}
}
}

View File

@@ -0,0 +1,3 @@
query ScrapeFreeonesPerformers($q: String!) {
scrapeFreeonesPerformerList(query: $q)
}

View File

@@ -0,0 +1,121 @@
query ListPerformerScrapers {
listPerformerScrapers {
id
name
performer {
urls
supported_scrapes
}
}
}
query ListSceneScrapers {
listSceneScrapers {
id
name
scene {
urls
supported_scrapes
}
}
}
query ListGalleryScrapers {
listGalleryScrapers {
id
name
gallery {
urls
supported_scrapes
}
}
}
query ListMovieScrapers {
listMovieScrapers {
id
name
movie {
urls
supported_scrapes
}
}
}
query ScrapeSingleStudio(
$source: ScraperSourceInput!
$input: ScrapeSingleStudioInput!
) {
scrapeSingleStudio(source: $source, input: $input) {
...ScrapedStudioData
}
}
query ScrapeSinglePerformer(
$source: ScraperSourceInput!
$input: ScrapeSinglePerformerInput!
) {
scrapeSinglePerformer(source: $source, input: $input) {
...ScrapedPerformerData
}
}
query ScrapeMultiPerformers(
$source: ScraperSourceInput!
$input: ScrapeMultiPerformersInput!
) {
scrapeMultiPerformers(source: $source, input: $input) {
...ScrapedPerformerData
}
}
query ScrapePerformerURL($url: String!) {
scrapePerformerURL(url: $url) {
...ScrapedPerformerData
}
}
query ScrapeSingleScene(
$source: ScraperSourceInput!
$input: ScrapeSingleSceneInput!
) {
scrapeSingleScene(source: $source, input: $input) {
...ScrapedSceneData
}
}
query ScrapeMultiScenes(
$source: ScraperSourceInput!
$input: ScrapeMultiScenesInput!
) {
scrapeMultiScenes(source: $source, input: $input) {
...ScrapedSceneData
}
}
query ScrapeSceneURL($url: String!) {
scrapeSceneURL(url: $url) {
...ScrapedSceneData
}
}
query ScrapeSingleGallery(
$source: ScraperSourceInput!
$input: ScrapeSingleGalleryInput!
) {
scrapeSingleGallery(source: $source, input: $input) {
...ScrapedGalleryData
}
}
query ScrapeGalleryURL($url: String!) {
scrapeGalleryURL(url: $url) {
...ScrapedGalleryData
}
}
query ScrapeMovieURL($url: String!) {
scrapeMovieURL(url: $url) {
...ScrapedMovieData
}
}

View File

@@ -0,0 +1,9 @@
query SystemStatus {
systemStatus {
databaseSchema
databasePath
appSchema
status
configPath
}
}

View File

@@ -0,0 +1,14 @@
query FindStudios($filter: FindFilterType, $studio_filter: StudioFilterType) {
findStudios(filter: $filter, studio_filter: $studio_filter) {
count
studios {
...StudioData
}
}
}
query FindStudio($id: ID!) {
findStudio(id: $id) {
...StudioData
}
}

View File

@@ -0,0 +1,14 @@
query FindTags($filter: FindFilterType, $tag_filter: TagFilterType) {
findTags(filter: $filter, tag_filter: $tag_filter) {
count
tags {
...TagData
}
}
}
query FindTag($id: ID!) {
findTag(id: $id) {
...TagData
}
}

View File

@@ -7,8 +7,6 @@ subscription JobsSubscribe {
subTasks
description
progress
error
startTime
}
}
}

View File

@@ -4,27 +4,6 @@ type Query {
findSavedFilter(id: ID!): SavedFilter
findSavedFilters(mode: FilterMode): [SavedFilter!]!
findDefaultFilter(mode: FilterMode!): SavedFilter
@deprecated(reason: "default filter now stored in UI config")
"Find a file by its id or path"
findFile(id: ID, path: String): BaseFile!
"Queries for Files"
findFiles(
file_filter: FileFilterType
filter: FindFilterType
ids: [ID!]
): FindFilesResultType!
"Find a file by its id or path"
findFolder(id: ID, path: String): Folder!
"Queries for Files"
findFolders(
folder_filter: FolderFilterType
filter: FindFilterType
ids: [ID!]
): FindFoldersResultType!
"Find a scene by ID or Checksum"
findScene(id: ID, checksum: String): Scene
@@ -33,8 +12,7 @@ type Query {
"A function which queries Scene objects"
findScenes(
scene_filter: SceneFilterType
scene_ids: [Int!] @deprecated(reason: "use ids")
ids: [ID!]
scene_ids: [Int!]
filter: FindFilterType
): FindScenesResultType!
@@ -65,7 +43,6 @@ type Query {
findSceneMarkers(
scene_marker_filter: SceneMarkerFilterType
filter: FindFilterType
ids: [ID!]
): FindSceneMarkersResultType!
findImage(id: ID, checksum: String): Image
@@ -73,8 +50,7 @@ type Query {
"A function which queries Scene objects"
findImages(
image_filter: ImageFilterType
image_ids: [Int!] @deprecated(reason: "use ids")
ids: [ID!]
image_ids: [Int!]
filter: FindFilterType
): FindImagesResultType!
@@ -84,8 +60,7 @@ type Query {
findPerformers(
performer_filter: PerformerFilterType
filter: FindFilterType
performer_ids: [Int!] @deprecated(reason: "use ids")
ids: [ID!]
performer_ids: [Int!]
): FindPerformersResultType!
"Find a studio by ID"
@@ -94,39 +69,26 @@ type Query {
findStudios(
studio_filter: StudioFilterType
filter: FindFilterType
ids: [ID!]
): FindStudiosResultType!
"Find a movie by ID"
findMovie(id: ID!): Movie @deprecated(reason: "Use findGroup instead")
findMovie(id: ID!): Movie
"A function which queries Movie objects"
findMovies(
movie_filter: MovieFilterType
filter: FindFilterType
ids: [ID!]
): FindMoviesResultType! @deprecated(reason: "Use findGroups instead")
"Find a group by ID"
findGroup(id: ID!): Group
"A function which queries Group objects"
findGroups(
group_filter: GroupFilterType
filter: FindFilterType
ids: [ID!]
): FindGroupsResultType!
): FindMoviesResultType!
findGallery(id: ID!): Gallery
findGalleries(
gallery_filter: GalleryFilterType
filter: FindFilterType
ids: [ID!]
): FindGalleriesResultType!
findTag(id: ID!): Tag
findTags(
tag_filter: TagFilterType
filter: FindFilterType
ids: [ID!]
): FindTagsResultType!
"Retrieve random scene markers for the wall"
@@ -147,6 +109,14 @@ type Query {
"List available scrapers"
listScrapers(types: [ScrapeContentType!]!): [Scraper!]!
listPerformerScrapers: [Scraper!]!
@deprecated(reason: "Use listScrapers(types: [PERFORMER])")
listSceneScrapers: [Scraper!]!
@deprecated(reason: "Use listScrapers(types: [SCENE])")
listGalleryScrapers: [Scraper!]!
@deprecated(reason: "Use listScrapers(types: [GALLERY])")
listMovieScrapers: [Scraper!]!
@deprecated(reason: "Use listScrapers(types: [MOVIE])")
"Scrape for a single scene"
scrapeSingleScene(
@@ -165,12 +135,6 @@ type Query {
input: ScrapeSingleStudioInput!
): [ScrapedStudio!]!
"Scrape for a single tag"
scrapeSingleTag(
source: ScraperSourceInput!
input: ScrapeSingleTagInput!
): [ScrapedTag!]!
"Scrape for a single performer"
scrapeSinglePerformer(
source: ScraperSourceInput!
@@ -192,19 +156,7 @@ type Query {
scrapeSingleMovie(
source: ScraperSourceInput!
input: ScrapeSingleMovieInput!
): [ScrapedMovie!]! @deprecated(reason: "Use scrapeSingleGroup instead")
"Scrape for a single group"
scrapeSingleGroup(
source: ScraperSourceInput!
input: ScrapeSingleGroupInput!
): [ScrapedGroup!]!
"Scrape for a single image"
scrapeSingleImage(
source: ScraperSourceInput!
input: ScrapeSingleImageInput!
): [ScrapedImage!]!
): [ScrapedMovie!]!
"Scrapes content based on a URL"
scrapeURL(url: String!, ty: ScrapeContentType!): ScrapedContent
@@ -215,13 +167,29 @@ type Query {
scrapeSceneURL(url: String!): ScrapedScene
"Scrapes a complete gallery record based on a URL"
scrapeGalleryURL(url: String!): ScrapedGallery
"Scrapes a complete image record based on a URL"
scrapeImageURL(url: String!): ScrapedImage
"Scrapes a complete movie record based on a URL"
scrapeMovieURL(url: String!): ScrapedMovie
@deprecated(reason: "Use scrapeGroupURL instead")
"Scrapes a complete group record based on a URL"
scrapeGroupURL(url: String!): ScrapedGroup
"Scrape a list of performers based on name"
scrapePerformerList(scraper_id: ID!, query: String!): [ScrapedPerformer!]!
@deprecated(reason: "use scrapeSinglePerformer")
"Scrapes a complete performer record based on a scrapePerformerList result"
scrapePerformer(
scraper_id: ID!
scraped_performer: ScrapedPerformerInput!
): ScrapedPerformer @deprecated(reason: "use scrapeSinglePerformer")
"Scrapes a complete scene record based on an existing scene"
scrapeScene(scraper_id: ID!, scene: SceneUpdateInput!): ScrapedScene
@deprecated(reason: "use scrapeSingleScene")
"Scrapes a complete gallery record based on an existing gallery"
scrapeGallery(scraper_id: ID!, gallery: GalleryUpdateInput!): ScrapedGallery
@deprecated(reason: "use scrapeSingleGallery")
"Scrape a list of performers from a query"
scrapeFreeonesPerformerList(query: String!): [String!]!
@deprecated(
reason: "use scrapeSinglePerformer with scraper_id = builtin_freeones"
)
# Plugins
"List loaded plugins"
@@ -229,12 +197,6 @@ type Query {
"List available plugin operations"
pluginTasks: [PluginTask!]
# Packages
"List installed packages"
installedPackages(type: PackageType!): [Package!]!
"List available packages"
availablePackages(type: PackageType!, source: String!): [Package!]!
# Config
"Returns the current, complete configuration"
configuration: ConfigResult!
@@ -258,16 +220,16 @@ type Query {
# Get everything
allScenes: [Scene!]! @deprecated(reason: "Use findScenes instead")
allScenes: [Scene!]!
allSceneMarkers: [SceneMarker!]!
@deprecated(reason: "Use findSceneMarkers instead")
allImages: [Image!]! @deprecated(reason: "Use findImages instead")
allGalleries: [Gallery!]! @deprecated(reason: "Use findGalleries instead")
allImages: [Image!]!
allGalleries: [Gallery!]!
allStudios: [Studio!]!
allMovies: [Movie!]!
allTags: [Tag!]!
# @deprecated
allPerformers: [Performer!]!
allTags: [Tag!]! @deprecated(reason: "Use findTags instead")
allStudios: [Studio!]! @deprecated(reason: "Use findStudios instead")
allMovies: [Movie!]! @deprecated(reason: "Use findGroups instead")
# Get everything with minimal metadata
@@ -280,12 +242,7 @@ type Query {
type Mutation {
setup(input: SetupInput!): Boolean!
"Migrates the schema to the required version. Returns the job ID"
migrate(input: MigrateInput!): ID!
"Downloads and installs ffmpeg and ffprobe binaries into the configuration directory. Returns the job ID."
downloadFFMpeg: ID!
migrate(input: MigrateInput!): Boolean!
sceneCreate(input: SceneCreateInput!): Scene
sceneUpdate(input: SceneUpdateInput!): Scene
@@ -296,47 +253,24 @@ type Mutation {
scenesUpdate(input: [SceneUpdateInput!]!): [Scene]
"Increments the o-counter for a scene. Returns the new value"
sceneIncrementO(id: ID!): Int! @deprecated(reason: "Use sceneAddO instead")
sceneIncrementO(id: ID!): Int!
"Decrements the o-counter for a scene. Returns the new value"
sceneDecrementO(id: ID!): Int! @deprecated(reason: "Use sceneRemoveO instead")
"Increments the o-counter for a scene. Uses the current time if none provided."
sceneAddO(id: ID!, times: [Timestamp!]): HistoryMutationResult!
"Decrements the o-counter for a scene, removing the last recorded time if specific time not provided. Returns the new value"
sceneDeleteO(id: ID!, times: [Timestamp!]): HistoryMutationResult!
sceneDecrementO(id: ID!): Int!
"Resets the o-counter for a scene to 0. Returns the new value"
sceneResetO(id: ID!): Int!
"Sets the resume time point (if provided) and adds the provided duration to the scene's play duration"
sceneSaveActivity(id: ID!, resume_time: Float, playDuration: Float): Boolean!
"Resets the resume time point and play duration"
sceneResetActivity(
id: ID!
reset_resume: Boolean
reset_duration: Boolean
): Boolean!
"Increments the play count for the scene. Returns the new play count value."
sceneIncrementPlayCount(id: ID!): Int!
@deprecated(reason: "Use sceneAddPlay instead")
"Increments the play count for the scene. Uses the current time if none provided."
sceneAddPlay(id: ID!, times: [Timestamp!]): HistoryMutationResult!
"Decrements the play count for the scene, removing the specific times or the last recorded time if not provided."
sceneDeletePlay(id: ID!, times: [Timestamp!]): HistoryMutationResult!
"Resets the play count for a scene to 0. Returns the new play count value."
sceneResetPlayCount(id: ID!): Int!
"Generates screenshot at specified time in seconds. Leave empty to generate default screenshot"
sceneGenerateScreenshot(id: ID!, at: Float): String!
sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker
sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker
bulkSceneMarkerUpdate(input: BulkSceneMarkerUpdateInput!): [SceneMarker!]
sceneMarkerDestroy(id: ID!): Boolean!
sceneMarkersDestroy(ids: [ID!]!): Boolean!
sceneAssignFile(input: AssignSceneFileInput!): Boolean!
@@ -361,8 +295,6 @@ type Mutation {
addGalleryImages(input: GalleryAddInput!): Boolean!
removeGalleryImages(input: GalleryRemoveInput!): Boolean!
setGalleryCover(input: GallerySetCoverInput!): Boolean!
resetGalleryCover(input: GalleryResetCoverInput!): Boolean!
galleryChapterCreate(input: GalleryChapterCreateInput!): GalleryChapter
galleryChapterUpdate(input: GalleryChapterUpdateInput!): GalleryChapter
@@ -373,43 +305,23 @@ type Mutation {
performerDestroy(input: PerformerDestroyInput!): Boolean!
performersDestroy(ids: [ID!]!): Boolean!
bulkPerformerUpdate(input: BulkPerformerUpdateInput!): [Performer!]
performerMerge(input: PerformerMergeInput!): Performer!
studioCreate(input: StudioCreateInput!): Studio
studioUpdate(input: StudioUpdateInput!): Studio
studioDestroy(input: StudioDestroyInput!): Boolean!
studiosDestroy(ids: [ID!]!): Boolean!
bulkStudioUpdate(input: BulkStudioUpdateInput!): [Studio!]
movieCreate(input: MovieCreateInput!): Movie
@deprecated(reason: "Use groupCreate instead")
movieUpdate(input: MovieUpdateInput!): Movie
@deprecated(reason: "Use groupUpdate instead")
movieDestroy(input: MovieDestroyInput!): Boolean!
@deprecated(reason: "Use groupDestroy instead")
moviesDestroy(ids: [ID!]!): Boolean!
@deprecated(reason: "Use groupsDestroy instead")
bulkMovieUpdate(input: BulkMovieUpdateInput!): [Movie!]
@deprecated(reason: "Use bulkGroupUpdate instead")
groupCreate(input: GroupCreateInput!): Group
groupUpdate(input: GroupUpdateInput!): Group
groupDestroy(input: GroupDestroyInput!): Boolean!
groupsDestroy(ids: [ID!]!): Boolean!
bulkGroupUpdate(input: BulkGroupUpdateInput!): [Group!]
addGroupSubGroups(input: GroupSubGroupAddInput!): Boolean!
removeGroupSubGroups(input: GroupSubGroupRemoveInput!): Boolean!
"Reorder sub groups within a group. Returns true if successful."
reorderSubGroups(input: ReorderSubGroupsInput!): Boolean!
tagCreate(input: TagCreateInput!): Tag
tagUpdate(input: TagUpdateInput!): Tag
tagDestroy(input: TagDestroyInput!): Boolean!
tagsDestroy(ids: [ID!]!): Boolean!
tagsMerge(input: TagsMergeInput!): Tag
bulkTagUpdate(input: BulkTagUpdateInput!): [Tag!]
"""
Moves the given files to the given destination. Returns true if successful.
@@ -422,20 +334,11 @@ type Mutation {
"""
moveFiles(input: MoveFilesInput!): Boolean!
deleteFiles(ids: [ID!]!): Boolean!
"Deletes file entries from the database without deleting the files from the filesystem"
destroyFiles(ids: [ID!]!): Boolean!
fileSetFingerprints(input: FileSetFingerprintsInput!): Boolean!
"Reveal the file in the system file manager"
revealFileInFileManager(id: ID!): Boolean!
"Reveal the folder in the system file manager"
revealFolderInFileManager(id: ID!): Boolean!
# Saved filters
saveFilter(input: SaveFilterInput!): SavedFilter!
destroySavedFilter(input: DestroyFilterInput!): Boolean!
setDefaultFilter(input: SetDefaultFilterInput!): Boolean!
@deprecated(reason: "now uses UI config")
"Change general configuration options"
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
@@ -446,19 +349,9 @@ type Mutation {
input: ConfigDefaultSettingsInput!
): ConfigDefaultSettingsResult!
"overwrites the entire plugin configuration for the given plugin"
configurePlugin(plugin_id: ID!, input: Map!): Map!
"""
overwrites the UI configuration
if input is provided, then the entire UI configuration is replaced
if partial is provided, then the partial UI configuration is merged into the existing UI configuration
"""
configureUI(input: Map, partial: Map): Map!
"""
sets a single UI key value
key is a dot separated path to the value
"""
# overwrites the entire UI configuration
configureUI(input: Map!): Map!
# sets a single UI key value
configureUISetting(key: String!, value: Any): Map!
"Generate and set (or clear) API key"
@@ -482,8 +375,6 @@ type Mutation {
metadataAutoTag(input: AutoTagMetadataInput!): ID!
"Clean metadata. Returns the job ID"
metadataClean(input: CleanMetadataInput!): ID!
"Clean generated files. Returns the job ID"
metadataCleanGenerated(input: CleanGeneratedInput!): ID!
"Identifies scenes using scrapers. Returns the job ID"
metadataIdentify(input: IdentifyMetadataInput!): ID!
@@ -503,60 +394,14 @@ type Mutation {
"Reload scrapers"
reloadScrapers: Boolean!
"""
Enable/disable plugins - enabledMap is a map of plugin IDs to enabled booleans.
Plugins not in the map are not affected.
"""
setPluginsEnabled(enabledMap: BoolMap!): Boolean!
"""
Run a plugin task.
If task_name is provided, then the task must exist in the plugin config and the tasks configuration
will be used to run the plugin.
If no task_name is provided, then the plugin will be executed with the arguments provided only.
Returns the job ID
"""
"Run plugin task. Returns the job ID"
runPluginTask(
plugin_id: ID!
"if provided, then the default args will be applied"
task_name: String
"displayed in the task queue"
description: String
args: [PluginArgInput!] @deprecated(reason: "Use args_map instead")
args_map: Map
task_name: String!
args: [PluginArgInput!]
): ID!
"""
Runs a plugin operation. The operation is run immediately and does not use the job queue.
Returns a map of the result.
"""
runPluginOperation(plugin_id: ID!, args: Map): Any
reloadPlugins: Boolean!
"""
Installs the given packages.
If a package is already installed, it will be updated if needed..
If an error occurs when installing a package, the job will continue to install the remaining packages.
Returns the job ID
"""
installPackages(type: PackageType!, packages: [PackageSpecInput!]!): ID!
"""
Updates the given packages.
If a package is not installed, it will not be installed.
If a package does not need to be updated, it will not be updated.
If no packages are provided, all packages of the given type will be updated.
If an error occurs when updating a package, the job will continue to update the remaining packages.
Returns the job ID.
"""
updatePackages(type: PackageType!, packages: [PackageSpecInput!]): ID!
"""
Uninstalls the given packages.
If an error occurs when uninstalling a package, the job will continue to uninstall the remaining packages.
Returns the job ID
"""
uninstallPackages(type: PackageType!, packages: [PackageSpecInput!]!): ID!
stopJob(job_id: ID!): Boolean!
stopAllJobs: Boolean!
@@ -583,8 +428,6 @@ type Mutation {
stashBoxBatchPerformerTag(input: StashBoxBatchTagInput!): String!
"Run batch studio tag task. Returns the job ID."
stashBoxBatchStudioTag(input: StashBoxBatchTagInput!): String!
"Run batch tag tag task. Returns the job ID."
stashBoxBatchTagTag(input: StashBoxBatchTagInput!): String!
"Enables DLNA for an optional duration. Has no effect if DLNA is enabled by default"
enableDLNA(input: EnableDLNAInput!): Boolean!

View File

@@ -2,8 +2,6 @@ input SetupInput {
"Empty to indicate $HOME/.stash/config.yml default"
configLocation: String!
stashes: [StashConfigInput!]!
"True if SFW content mode is enabled"
sfwContentMode: Boolean
"Empty to indicate default"
databaseFile: String!
"Empty to indicate default"
@@ -69,26 +67,18 @@ input ConfigGeneralInput {
databasePath: String
"Path to backup directory"
backupDirectoryPath: String
"Path to trash directory - if set, deleted files will be moved here instead of being permanently deleted"
deleteTrashPath: String
"Path to generated files"
generatedPath: String
"Path to import/export files"
metadataPath: String
"Path to scrapers"
scrapersPath: String
"Path to plugins"
pluginsPath: String
"Path to cache"
cachePath: String
"Path to blobs - required for filesystem blob storage"
blobsPath: String
"Where to store blobs"
blobsStorage: BlobsStorageType
"Path to the ffmpeg binary. If empty, stash will attempt to find it in the path or config directory"
ffmpegPath: String
"Path to the ffprobe binary. If empty, stash will attempt to find it in the path or config directory"
ffprobePath: String
"Whether to calculate MD5 checksums for scene video files"
calculateMD5: Boolean
"Hash algorithm to use for generated file naming"
@@ -149,6 +139,8 @@ input ConfigGeneralInput {
password: String
"Maximum session cookie age"
maxSessionAge: Int
"Comma separated list of proxies to allow traffic from"
trustedProxies: [String!] @deprecated(reason: "no longer supported")
"Name of the log file"
logFile: String
"Whether to also output to stderr"
@@ -157,8 +149,6 @@ input ConfigGeneralInput {
logLevel: String
"Whether to log http access"
logAccess: Boolean
"Maximum log size"
logFileMaxSize: Int
"True if galleries should be created from folders with images"
createGalleriesFromFolders: Boolean
"Regex used to identify images as gallery covers"
@@ -175,27 +165,25 @@ input ConfigGeneralInput {
imageExcludes: [String!]
"Custom Performer Image Location"
customPerformerImageLocation: String
"Scraper user agent string"
scraperUserAgent: String
@deprecated(
reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead"
)
"Scraper CDP path. Path to chrome executable or remote address"
scraperCDPPath: String
@deprecated(
reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead"
)
"Whether the scraper should check for invalid certificates"
scraperCertCheck: Boolean
@deprecated(
reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead"
)
"Stash-box instances used for tagging"
stashBoxes: [StashBoxInput!]
"Python path - resolved using path if unset"
pythonPath: String
"Source of scraper packages"
scraperPackageSources: [PackageSourceInput!]
"Source of plugin packages"
pluginPackageSources: [PackageSourceInput!]
"Size of the longest dimension for each sprite in pixels"
spriteScreenshotSize: Int
"True if sprite generation should use the sprite interval and min/max sprites settings instead of the default"
useCustomSpriteInterval: Boolean
"Time between two different scrubber sprites in seconds - only used if useCustomSpriteInterval is true"
spriteInterval: Float
"Minimum number of sprites to be generated - only used if useCustomSpriteInterval is true"
minimumSprites: Int
"Minimum number of sprites to be generated - only used if useCustomSpriteInterval is true"
maximumSprites: Int
}
type ConfigGeneralResult {
@@ -205,8 +193,6 @@ type ConfigGeneralResult {
databasePath: String!
"Path to backup directory"
backupDirectoryPath: String!
"Path to trash directory - if set, deleted files will be moved here instead of being permanently deleted"
deleteTrashPath: String!
"Path to generated files"
generatedPath: String!
"Path to import/export files"
@@ -215,18 +201,12 @@ type ConfigGeneralResult {
configFilePath: String!
"Path to scrapers"
scrapersPath: String!
"Path to plugins"
pluginsPath: String!
"Path to cache"
cachePath: String!
"Path to blobs - required for filesystem blob storage"
blobsPath: String!
"Where to store blobs"
blobsStorage: BlobsStorageType!
"Path to the ffmpeg binary. If empty, stash will attempt to find it in the path or config directory"
ffmpegPath: String!
"Path to the ffprobe binary. If empty, stash will attempt to find it in the path or config directory"
ffprobePath: String!
"Whether to calculate MD5 checksums for scene video files"
calculateMD5: Boolean!
"Hash algorithm to use for generated file naming"
@@ -289,6 +269,8 @@ type ConfigGeneralResult {
password: String!
"Maximum session cookie age"
maxSessionAge: Int!
"Comma separated list of proxies to allow traffic from"
trustedProxies: [String!] @deprecated(reason: "no longer supported")
"Name of the log file"
logFile: String
"Whether to also output to stderr"
@@ -297,18 +279,6 @@ type ConfigGeneralResult {
logLevel: String!
"Whether to log http access"
logAccess: Boolean!
"Maximum log size"
logFileMaxSize: Int!
"True if sprite generation should use the sprite interval and min/max sprites settings instead of the default"
useCustomSpriteInterval: Boolean!
"Time between two different scrubber sprites in seconds - only used if useCustomSpriteInterval is true"
spriteInterval: Float!
"Minimum number of sprites to be generated - only used if useCustomSpriteInterval is true"
minimumSprites: Int!
"Maximum number of sprites to be generated - only used if useCustomSpriteInterval is true"
maximumSprites: Int!
"Size of the longest dimension for each sprite in pixels"
spriteScreenshotSize: Int!
"Array of video file extensions"
videoExtensions: [String!]!
"Array of image file extensions"
@@ -325,15 +295,19 @@ type ConfigGeneralResult {
imageExcludes: [String!]!
"Custom Performer Image Location"
customPerformerImageLocation: String
"Scraper user agent string"
scraperUserAgent: String
@deprecated(reason: "use ConfigResult.scraping instead")
"Scraper CDP path. Path to chrome executable or remote address"
scraperCDPPath: String
@deprecated(reason: "use ConfigResult.scraping instead")
"Whether the scraper should check for invalid certificates"
scraperCertCheck: Boolean!
@deprecated(reason: "use ConfigResult.scraping instead")
"Stash-box instances used for tagging"
stashBoxes: [StashBox!]!
"Python path - resolved using path if unset"
pythonPath: String!
"Source of scraper packages"
scraperPackageSources: [PackageSource!]!
"Source of plugin packages"
pluginPackageSources: [PackageSource!]!
}
input ConfigDisableDropdownCreateInput {
@@ -341,7 +315,6 @@ input ConfigDisableDropdownCreateInput {
tag: Boolean
studio: Boolean
movie: Boolean
gallery: Boolean
}
enum ImageLightboxDisplayMode {
@@ -362,7 +335,6 @@ input ConfigImageLightboxInput {
resetZoomOnNav: Boolean
scrollMode: ImageLightboxScrollMode
scrollAttemptsBeforeChange: Int
disableAnimation: Boolean
}
type ConfigImageLightboxResult {
@@ -372,13 +344,9 @@ type ConfigImageLightboxResult {
resetZoomOnNav: Boolean
scrollMode: ImageLightboxScrollMode
scrollAttemptsBeforeChange: Int!
disableAnimation: Boolean
}
input ConfigInterfaceInput {
"True if SFW content mode is enabled"
sfwContentMode: Boolean
"Ordered list of items that should be shown in the menu"
menuItems: [String!]
@@ -417,12 +385,12 @@ input ConfigInterfaceInput {
customLocales: String
customLocalesEnabled: Boolean
"When true, disables all customizations (plugins, CSS, JavaScript, locales) for troubleshooting"
disableCustomizations: Boolean
"Interface language"
language: String
"Slideshow Delay"
slideshowDelay: Int @deprecated(reason: "Use imageLightbox.slideshowDelay")
imageLightbox: ConfigImageLightboxInput
"Set to true to disable creating new objects via the dropdown menus"
@@ -445,13 +413,9 @@ type ConfigDisableDropdownCreate {
tag: Boolean!
studio: Boolean!
movie: Boolean!
gallery: Boolean!
}
type ConfigInterfaceResult {
"True if SFW content mode is enabled"
sfwContentMode: Boolean!
"Ordered list of items that should be shown in the menu"
menuItems: [String!]
@@ -494,16 +458,18 @@ type ConfigInterfaceResult {
customLocales: String
customLocalesEnabled: Boolean
"When true, disables all customizations (plugins, CSS, JavaScript, locales) for troubleshooting"
disableCustomizations: Boolean
"Interface language"
language: String
"Slideshow Delay"
slideshowDelay: Int @deprecated(reason: "Use imageLightbox.slideshowDelay")
imageLightbox: ConfigImageLightboxResult!
"Fields are true if creating via dropdown menus are disabled"
disableDropdownCreate: ConfigDisableDropdownCreate!
disabledDropdownCreate: ConfigDisableDropdownCreate!
@deprecated(reason: "Use disableDropdownCreate")
"Handy Connection Key"
handyKey: String
@@ -517,8 +483,6 @@ input ConfigDLNAInput {
serverName: String
"True if DLNA service should be enabled by default"
enabled: Boolean
"Defaults to 1338"
port: Int
"List of IPs whitelisted for DLNA service"
whitelistedIPs: [String!]
"List of interfaces to run DLNA on. Empty for all"
@@ -531,8 +495,6 @@ type ConfigDLNAResult {
serverName: String!
"True if DLNA service should be enabled by default"
enabled: Boolean!
"Defaults to 1338"
port: Int!
"List of IPs whitelisted for DLNA service"
whitelistedIPs: [String!]!
"List of interfaces to run DLNA on. Empty for all"
@@ -595,7 +557,6 @@ type ConfigResult {
scraping: ConfigScrapingResult!
defaults: ConfigDefaultSettingsResult!
ui: Map!
plugins(include: [ID!]): PluginConfigMap!
}
"Directory structure of a path"

View File

@@ -6,18 +6,9 @@ type Fingerprint {
type Folder {
id: ID!
path: String!
basename: String!
parent_folder_id: ID @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder
"Returns all parent folders in order from immediate parent to top-level"
parent_folders: [Folder!]!
zip_file: BasicFile
"Returns direct sub-folders"
sub_folders: [Folder!]!
parent_folder_id: ID
zip_file_id: ID
mod_time: Time!
@@ -30,37 +21,12 @@ interface BaseFile {
path: String!
basename: String!
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder!
zip_file: BasicFile
parent_folder_id: ID!
zip_file_id: ID
mod_time: Time!
size: Int64!
fingerprint(type: String!): String
fingerprints: [Fingerprint!]!
created_at: Time!
updated_at: Time!
}
type BasicFile implements BaseFile {
id: ID!
path: String!
basename: String!
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder!
zip_file: BasicFile
mod_time: Time!
size: Int64!
fingerprint(type: String!): String
fingerprints: [Fingerprint!]!
created_at: Time!
@@ -72,16 +38,12 @@ type VideoFile implements BaseFile {
path: String!
basename: String!
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder!
zip_file: BasicFile
parent_folder_id: ID!
zip_file_id: ID
mod_time: Time!
size: Int64!
fingerprint(type: String!): String
fingerprints: [Fingerprint!]!
format: String!
@@ -102,19 +64,14 @@ type ImageFile implements BaseFile {
path: String!
basename: String!
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder!
zip_file: BasicFile
parent_folder_id: ID!
zip_file_id: ID
mod_time: Time!
size: Int64!
fingerprint(type: String!): String
fingerprints: [Fingerprint!]!
format: String!
width: Int!
height: Int!
@@ -129,16 +86,12 @@ type GalleryFile implements BaseFile {
path: String!
basename: String!
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder!
zip_file: BasicFile
parent_folder_id: ID!
zip_file_id: ID
mod_time: Time!
size: Int64!
fingerprint(type: String!): String
fingerprints: [Fingerprint!]!
created_at: Time!
@@ -156,34 +109,3 @@ input MoveFilesInput {
"valid only for single file id. If empty, existing basename is used"
destination_basename: String
}
input SetFingerprintsInput {
type: String!
"a null value will remove the fingerprint"
value: String
}
input FileSetFingerprintsInput {
id: ID!
"only supplied fingerprint types will be modified"
fingerprints: [SetFingerprintsInput!]!
}
type FindFilesResultType {
count: Int!
"Total megapixels of any image files"
megapixels: Float!
"Total duration in seconds of any video files"
duration: Float!
"Total file size in bytes"
size: Int!
files: [BaseFile!]!
}
type FindFoldersResultType {
count: Int!
folders: [Folder!]!
}

View File

@@ -8,18 +8,6 @@ input FindFilterType {
page: Int
"use per_page = -1 to indicate all results. Defaults to 25."
per_page: Int
# TODO - this should be refactored to not use a string
sort: String
direction: SortDirectionEnum
}
type SavedFindFilterType {
q: String
page: Int
"""
use per_page = -1 to indicate all results. Defaults to 25.
"""
per_page: Int
sort: String
direction: SortDirectionEnum
}
@@ -62,64 +50,19 @@ input ResolutionCriterionInput {
modifier: CriterionModifier!
}
enum OrientationEnum {
"Landscape"
LANDSCAPE
"Portrait"
PORTRAIT
"Square"
SQUARE
}
input OrientationCriterionInput {
value: [OrientationEnum!]!
}
input DuplicationCriterionInput {
duplicated: Boolean @deprecated(reason: "Use phash field instead")
"Currently unimplemented. Intended for phash distance matching."
input PHashDuplicationCriterionInput {
duplicated: Boolean
"Currently unimplemented"
distance: Int
"Filter by phash duplication"
phash: Boolean
"Filter by URL duplication"
url: Boolean
"Filter by Stash ID duplication"
stash_id: Boolean
"Filter by title duplication"
title: Boolean
}
input FileDuplicationCriterionInput {
duplicated: Boolean @deprecated(reason: "Use phash field instead")
"Currently unimplemented. Intended for phash distance matching."
distance: Int
"Filter by phash duplication"
phash: Boolean
}
input StashIDCriterionInput {
"""
If present, this value is treated as a predicate.
That is, it will filter based on stash_id with the matching endpoint
"""
endpoint: String
stash_id: String
modifier: CriterionModifier!
}
input StashIDsCriterionInput {
"""
If present, this value is treated as a predicate.
That is, it will filter based on stash_ids with the matching endpoint
"""
endpoint: String
stash_ids: [String]
modifier: CriterionModifier!
}
input CustomFieldCriterionInput {
field: String!
value: [Any!]
stash_id: String
modifier: CriterionModifier!
}
@@ -144,6 +87,8 @@ input PerformerFilterType {
country: StringCriterionInput
"Filter by eye color"
eye_color: StringCriterionInput
"Filter by height"
height: StringCriterionInput @deprecated(reason: "Use height_cm instead")
"Filter by height in cm"
height_cm: IntCriterionInput
"Filter by measurements"
@@ -152,15 +97,10 @@ input PerformerFilterType {
fake_tits: StringCriterionInput
"Filter by penis length value"
penis_length: FloatCriterionInput
"Filter by circumcision"
"Filter by ciricumcision"
circumcised: CircumcisionCriterionInput
"Deprecated: use career_start and career_end. This filter is non-functional."
"Filter by career length"
career_length: StringCriterionInput
@deprecated(reason: "Use career_start and career_end")
"Filter by career start"
career_start: DateCriterionInput
"Filter by career end"
career_end: DateCriterionInput
"Filter by tattoos"
tattoos: StringCriterionInput
"Filter by piercings"
@@ -177,21 +117,20 @@ input PerformerFilterType {
tag_count: IntCriterionInput
"Filter by scene count"
scene_count: IntCriterionInput
"Filter by marker count (via scene)"
marker_count: IntCriterionInput
"Filter by image count"
image_count: IntCriterionInput
"Filter by gallery count"
gallery_count: IntCriterionInput
"Filter by play count"
play_count: IntCriterionInput
"Filter by o count"
o_counter: IntCriterionInput
"Filter by StashID"
stash_id: StringCriterionInput
@deprecated(reason: "Use stash_id_endpoint instead")
"Filter by StashID"
stash_id_endpoint: StashIDCriterionInput
@deprecated(reason: "use stash_ids_endpoint instead")
"Filter by StashIDs"
stash_ids_endpoint: StashIDsCriterionInput
"Filter by rating"
rating: IntCriterionInput
@deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter by url"
@@ -204,8 +143,6 @@ input PerformerFilterType {
death_year: IntCriterionInput
"Filter by studios where performer appears in scene/image/gallery"
studios: HierarchicalMultiCriterionInput
"Filter by groups where performer appears in scene"
groups: HierarchicalMultiCriterionInput
"Filter by performers where performer appears with another performer in scene/image/gallery"
performers: MultiCriterionInput
"Filter by autotag ignore value"
@@ -214,47 +151,31 @@ input PerformerFilterType {
birthdate: DateCriterionInput
"Filter by death date"
death_date: DateCriterionInput
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related images that meet this criteria"
images_filter: ImageFilterType
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by related tags that meet this criteria"
tags_filter: TagFilterType
"Filter by related scene markers (via scene) that meet this criteria"
markers_filter: SceneMarkerFilterType
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
custom_fields: [CustomFieldCriterionInput!]
}
input SceneMarkerFilterType {
"Filter to only include scene markers with this tag"
tag_id: ID @deprecated(reason: "use tags filter instead")
"Filter to only include scene markers with these tags"
tags: HierarchicalMultiCriterionInput
"Filter to only include scene markers attached to a scene with these tags"
scene_tags: HierarchicalMultiCriterionInput
"Filter to only include scene markers with these performers"
performers: MultiCriterionInput
"Filter to only include scene markers from these scenes"
scenes: MultiCriterionInput
"Filter by duration (in seconds)"
duration: FloatCriterionInput
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
"Filter by scene date"
scene_date: DateCriterionInput
"Filter by scene creation time"
"Filter by cscene reation time"
scene_created_at: TimestampCriterionInput
"Filter by scene last update time"
"Filter by lscene ast update time"
scene_updated_at: TimestampCriterionInput
"Filter by related scenes that meet this criteria"
scene_filter: SceneFilterType
}
input SceneFilterType {
@@ -280,22 +201,19 @@ input SceneFilterType {
path: StringCriterionInput
"Filter by file count"
file_count: IntCriterionInput
"Filter by rating"
rating: IntCriterionInput
@deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter by organized"
organized: Boolean
"Filter by o-counter"
o_counter: IntCriterionInput
"Filter Scenes by duplication criteria"
duplicated: DuplicationCriterionInput
"Filter Scenes that have an exact phash match available"
duplicated: PHashDuplicationCriterionInput
"Filter by resolution"
resolution: ResolutionCriterionInput
"Filter by orientation"
orientation: OrientationCriterionInput
"Filter by frame rate"
framerate: IntCriterionInput
"Filter by bit rate"
bitrate: IntCriterionInput
"Filter by video codec"
video_codec: StringCriterionInput
"Filter by audio codec"
@@ -309,11 +227,7 @@ input SceneFilterType {
"Filter to only include scenes with this studio"
studios: HierarchicalMultiCriterionInput
"Filter to only include scenes with this movie"
movies: MultiCriterionInput @deprecated(reason: "use groups instead")
"Filter to only include scenes with this group"
groups: HierarchicalMultiCriterionInput
"Filter to only include scenes with this gallery"
galleries: MultiCriterionInput
movies: MultiCriterionInput
"Filter to only include scenes with these tags"
tags: HierarchicalMultiCriterionInput
"Filter by tag count"
@@ -329,12 +243,10 @@ input SceneFilterType {
"Filter by performer count"
performer_count: IntCriterionInput
"Filter by StashID"
stash_id: StringCriterionInput
@deprecated(reason: "Use stash_id_endpoint instead")
"Filter by StashID"
stash_id_endpoint: StashIDCriterionInput
@deprecated(reason: "use stash_ids_endpoint instead")
"Filter by StashIDs"
stash_ids_endpoint: StashIDsCriterionInput
"Filter by StashID count"
stash_id_count: IntCriterionInput
"Filter by url"
url: StringCriterionInput
"Filter by interactive"
@@ -349,47 +261,24 @@ input SceneFilterType {
play_count: IntCriterionInput
"Filter by play duration (in seconds)"
play_duration: IntCriterionInput
"Filter by scene last played time"
last_played_at: TimestampCriterionInput
"Filter by date"
date: DateCriterionInput
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by related performers that meet this criteria"
performers_filter: PerformerFilterType
"Filter by related studios that meet this criteria"
studios_filter: StudioFilterType
"Filter by related tags that meet this criteria"
tags_filter: TagFilterType
"Filter by related movies that meet this criteria"
movies_filter: MovieFilterType
@deprecated(reason: "use groups_filter instead")
"Filter by related groups that meet this criteria"
groups_filter: GroupFilterType
"Filter by related markers that meet this criteria"
markers_filter: SceneMarkerFilterType
"Filter by related files that meet this criteria"
files_filter: FileFilterType
custom_fields: [CustomFieldCriterionInput!]
}
input MovieFilterType {
AND: MovieFilterType
OR: MovieFilterType
NOT: MovieFilterType
name: StringCriterionInput
director: StringCriterionInput
synopsis: StringCriterionInput
"Filter by duration (in seconds)"
duration: IntCriterionInput
"Filter by rating"
rating: IntCriterionInput
@deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter to only include movies with this studio"
@@ -400,75 +289,12 @@ input MovieFilterType {
url: StringCriterionInput
"Filter to only include movies where performer appears in a scene"
performers: MultiCriterionInput
"Filter to only include movies with these tags"
tags: HierarchicalMultiCriterionInput
"Filter by tag count"
tag_count: IntCriterionInput
"Filter by date"
date: DateCriterionInput
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related studios that meet this criteria"
studios_filter: StudioFilterType
}
input GroupFilterType {
AND: GroupFilterType
OR: GroupFilterType
NOT: GroupFilterType
name: StringCriterionInput
director: StringCriterionInput
synopsis: StringCriterionInput
"Filter by duration (in seconds)"
duration: IntCriterionInput
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter to only include groups with this studio"
studios: HierarchicalMultiCriterionInput
"Filter to only include groups missing this property"
is_missing: String
"Filter by url"
url: StringCriterionInput
"Filter to only include groups where performer appears in a scene"
performers: MultiCriterionInput
"Filter to only include groups with these tags"
tags: HierarchicalMultiCriterionInput
"Filter by tag count"
tag_count: IntCriterionInput
"Filter by date"
date: DateCriterionInput
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
"Filter by o-counter"
o_counter: IntCriterionInput
"Filter by containing groups"
containing_groups: HierarchicalMultiCriterionInput
"Filter by sub groups"
sub_groups: HierarchicalMultiCriterionInput
"Filter by number of containing groups the group has"
containing_group_count: IntCriterionInput
"Filter by number of sub-groups the group has"
sub_group_count: IntCriterionInput
"Filter by number of scenes the group has"
scene_count: IntCriterionInput
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related studios that meet this criteria"
studios_filter: StudioFilterType
"Filter by custom fields"
custom_fields: [CustomFieldCriterionInput!]
}
input StudioFilterType {
@@ -481,52 +307,33 @@ input StudioFilterType {
"Filter to only include studios with this parent studio"
parents: MultiCriterionInput
"Filter by StashID"
stash_id: StringCriterionInput
@deprecated(reason: "Use stash_id_endpoint instead")
"Filter by StashID"
stash_id_endpoint: StashIDCriterionInput
@deprecated(reason: "use stash_ids_endpoint instead")
"Filter by StashIDs"
stash_ids_endpoint: StashIDsCriterionInput
"Filter to only include studios with these tags"
tags: HierarchicalMultiCriterionInput
"Filter to only include studios missing this property"
is_missing: String
"Filter by rating"
rating: IntCriterionInput
@deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter by favorite"
favorite: Boolean
"Filter by scene count"
scene_count: IntCriterionInput
"Filter by image count"
image_count: IntCriterionInput
"Filter by gallery count"
gallery_count: IntCriterionInput
"Filter by group count"
group_count: IntCriterionInput
"Filter by tag count"
tag_count: IntCriterionInput
"Filter by url"
url: StringCriterionInput
"Filter by studio aliases"
aliases: StringCriterionInput
"Filter by subsidiary studio count"
child_count: IntCriterionInput
"Filter by autotag ignore value"
ignore_auto_tag: Boolean
"Filter by organized"
organized: Boolean
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related images that meet this criteria"
images_filter: ImageFilterType
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by related groups that meet this criteria"
groups_filter: GroupFilterType
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
custom_fields: [CustomFieldCriterionInput!]
}
input GalleryFilterType {
@@ -548,6 +355,9 @@ input GalleryFilterType {
is_missing: String
"Filter to include/exclude galleries that were created from zip"
is_zip: Boolean
"Filter by rating"
rating: IntCriterionInput
@deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter by organized"
@@ -556,8 +366,6 @@ input GalleryFilterType {
average_resolution: ResolutionCriterionInput
"Filter to only include galleries that have chapters. `true` or `false`"
has_chapters: String
"Filter to only include galleries with these scenes"
scenes: MultiCriterionInput
"Filter to only include galleries with this studio"
studios: HierarchicalMultiCriterionInput
"Filter to only include galleries with these tags"
@@ -584,29 +392,6 @@ input GalleryFilterType {
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
"Filter by studio code"
code: StringCriterionInput
"Filter by photographer"
photographer: StringCriterionInput
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related images that meet this criteria"
images_filter: ImageFilterType
"Filter by related performers that meet this criteria"
performers_filter: PerformerFilterType
"Filter by related studios that meet this criteria"
studios_filter: StudioFilterType
"Filter by related tags that meet this criteria"
tags_filter: TagFilterType
"Filter by related files that meet this criteria"
files_filter: FileFilterType
"Filter by related folders that meet this criteria"
folders_filter: FolderFilterType
"Filter by parent folder of the zip or folder the gallery is in"
parent_folder: HierarchicalMultiCriterionInput
custom_fields: [CustomFieldCriterionInput!]
}
input TagFilterType {
@@ -617,15 +402,9 @@ input TagFilterType {
"Filter by tag name"
name: StringCriterionInput
"Filter by tag sort_name"
sort_name: StringCriterionInput
"Filter by tag aliases"
aliases: StringCriterionInput
"Filter by favorite"
favorite: Boolean
"Filter by tag description"
description: StringCriterionInput
@@ -644,15 +423,6 @@ input TagFilterType {
"Filter by number of performers with this tag"
performer_count: IntCriterionInput
"Filter by number of studios with this tag"
studio_count: IntCriterionInput
"Filter by number of movies with this tag"
movie_count: IntCriterionInput
"Filter by number of group with this tag"
group_count: IntCriterionInput
"Filter by number of markers with this tag"
marker_count: IntCriterionInput
@@ -665,41 +435,17 @@ input TagFilterType {
"Filter by number of parent tags the tag has"
parent_count: IntCriterionInput
"Filter by number of child tags the tag has"
"Filter by number f child tags the tag has"
child_count: IntCriterionInput
"Filter by autotag ignore value"
ignore_auto_tag: Boolean
"Filter by StashID"
stash_id_endpoint: StashIDCriterionInput
@deprecated(reason: "use stash_ids_endpoint instead")
"Filter by StashID"
stash_ids_endpoint: StashIDsCriterionInput
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related images that meet this criteria"
images_filter: ImageFilterType
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by related groups that meet this criteria"
groups_filter: GroupFilterType
"Filter by related performers that meet this criteria"
performers_filter: PerformerFilterType
"Filter by related studios that meet this criteria"
studios_filter: StudioFilterType
"Filter by related scene markers that meet this criteria"
markers_filter: SceneMarkerFilterType
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
custom_fields: [CustomFieldCriterionInput!]
}
input ImageFilterType {
@@ -708,18 +454,18 @@ input ImageFilterType {
NOT: ImageFilterType
title: StringCriterionInput
details: StringCriterionInput
" Filter by image id"
id: IntCriterionInput
"Filter by file checksum"
checksum: StringCriterionInput
"Filter by file phash distance"
phash_distance: PhashDistanceCriterionInput
"Filter by path"
path: StringCriterionInput
"Filter by file count"
file_count: IntCriterionInput
"Filter by rating"
rating: IntCriterionInput
@deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter by date"
@@ -732,8 +478,6 @@ input ImageFilterType {
o_counter: IntCriterionInput
"Filter by resolution"
resolution: ResolutionCriterionInput
"Filter by orientation"
orientation: OrientationCriterionInput
"Filter to only include images missing this property"
is_missing: String
"Filter to only include images with this studio"
@@ -750,130 +494,12 @@ input ImageFilterType {
performer_count: IntCriterionInput
"Filter images that have performers that have been favorited"
performer_favorite: Boolean
"Filter images by performer age at time of image"
performer_age: IntCriterionInput
"Filter to only include images with these galleries"
galleries: MultiCriterionInput
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
"Filter by studio code"
code: StringCriterionInput
"Filter by photographer"
photographer: StringCriterionInput
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by related performers that meet this criteria"
performers_filter: PerformerFilterType
"Filter by related studios that meet this criteria"
studios_filter: StudioFilterType
"Filter by related tags that meet this criteria"
tags_filter: TagFilterType
"Filter by related files that meet this criteria"
files_filter: FileFilterType
"Filter by custom fields"
custom_fields: [CustomFieldCriterionInput!]
}
input FileFilterType {
AND: FileFilterType
OR: FileFilterType
NOT: FileFilterType
path: StringCriterionInput
basename: StringCriterionInput
dir: StringCriterionInput
parent_folder: HierarchicalMultiCriterionInput
zip_file: MultiCriterionInput
"Filter by modification time"
mod_time: TimestampCriterionInput
"Filter files by duplication criteria (only phash applies to files)"
duplicated: FileDuplicationCriterionInput
"find files based on hash"
hashes: [FingerprintFilterInput!]
video_file_filter: VideoFileFilterInput
image_file_filter: ImageFileFilterInput
scene_count: IntCriterionInput
image_count: IntCriterionInput
gallery_count: IntCriterionInput
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related images that meet this criteria"
images_filter: ImageFilterType
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
}
input FolderFilterType {
AND: FolderFilterType
OR: FolderFilterType
NOT: FolderFilterType
path: StringCriterionInput
basename: StringCriterionInput
parent_folder: HierarchicalMultiCriterionInput
zip_file: MultiCriterionInput
"Filter by modification time"
mod_time: TimestampCriterionInput
gallery_count: IntCriterionInput
"Filter by files that meet this criteria"
files_filter: FileFilterType
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
}
input VideoFileFilterInput {
resolution: ResolutionCriterionInput
orientation: OrientationCriterionInput
framerate: IntCriterionInput
bitrate: IntCriterionInput
format: StringCriterionInput
video_codec: StringCriterionInput
audio_codec: StringCriterionInput
"in seconds"
duration: IntCriterionInput
captions: StringCriterionInput
interactive: Boolean
interactive_speed: IntCriterionInput
}
input ImageFileFilterInput {
format: StringCriterionInput
resolution: ResolutionCriterionInput
orientation: OrientationCriterionInput
}
input FingerprintFilterInput {
type: String!
value: String!
"Hamming distance - defaults to 0"
distance: Int
}
enum CriterionModifier {
@@ -928,12 +554,11 @@ input MultiCriterionInput {
input GenderCriterionInput {
value: GenderEnum
value_list: [GenderEnum!]
modifier: CriterionModifier!
}
input CircumcisionCriterionInput {
value: [CircumcisedEnum!]
value: [CircumisedEnum!]
modifier: CriterionModifier!
}
@@ -969,7 +594,6 @@ enum FilterMode {
GALLERIES
SCENE_MARKERS
MOVIES
GROUPS
TAGS
IMAGES
}
@@ -980,13 +604,6 @@ type SavedFilter {
name: String!
"JSON-encoded filter string"
filter: String!
@deprecated(reason: "use find_filter and object_filter instead")
find_filter: SavedFindFilterType
# maps to any of the AnyFilterInput types
# using a generic Map instead of creating and maintaining match types for inputs
object_filter: Map
# generic map for ui options
ui_options: Map
}
input SaveFilterInput {
@@ -994,10 +611,8 @@ input SaveFilterInput {
id: ID
mode: FilterMode!
name: String!
find_filter: FindFilterType
object_filter: Map
# generic map for ui options
ui_options: Map
"JSON-encoded filter string"
filter: String!
}
input DestroyFilterInput {
@@ -1006,9 +621,6 @@ input DestroyFilterInput {
input SetDefaultFilterInput {
mode: FilterMode!
"null to clear"
find_filter: FindFilterType
object_filter: Map
# generic map for ui options
ui_options: Map
"JSON-encoded filter string - null to clear"
filter: String
}

Some files were not shown because too many files have changed in this diff Show More