Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[plugin][movieFromScene] movieFromScene - Automatically creates new movies for scenes that have enough information #137

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Scanning|Scene.Create<br />Gallery.Create|[filenameParser](plugins/filenameParse
Scanning|Scene.Create|[pathParser](plugins/pathParser)|Updates scene info based on the file path.|v0.17
Scanning|Scene.Create|[titleFromFilename](plugins/titleFromFilename)|Sets the scene title to its filename|v0.17
Reporting||[TagGraph](plugins/tagGraph)|Creates a visual of the Tag relations.|v0.7
Movies|Scene.Update|[movieFromScene](plugins/movieFromScene)|Auto-create movies when scenes get updated.|v0.18

## Themes

Expand Down
48 changes: 48 additions & 0 deletions plugins/movieFromScene/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Auto-Create Movie From Scene Update
### A python plugin for Stash with GUI config.

Tested under: <br>
Stash v0.18.0 <br>
Python 3.10 with tkinter installed <br>
Windows 11

### Purpose of the plugin:
There are lots of scrapers which can retrieve information for scenes, but there are only a few of them came with movie scrapers. No one have patience to scrape the scene then copy the information from scenes to movies bit by bit. Therefore, this is where the plugin comes in handy: you specify the criteria when to automatically create a movie for your scene. Once the settings are set, and a scene has enough information to fit the criteria, it will automatically use the scene information to create a new movie for you.

### install instructions:
Drop the py_plugins folder and the "movieFromScene.yml" file in stash's plugin folder, and press the `Reload plugins` button in the Plugin settings. <p>

This plugin requires python 3.10 for the new "match" statement. Because "if elif...else..." is so lame! <br>
It also comes with a GUI that can help you set the criteria and run mode easily, but then you need to install "tkinter" in Python. Or you can just edit the config file manually.

### How to use it:
Once installed, you will find under the "Settings->Tasks->Plugin Tasks" a new task called "Auto-Create Movie From Scene" like below:
<p>
<img src="https://user-images.githubusercontent.com/22040708/211181083-e24a7685-073e-4f0c-a00f-872dfbe34ab4.png" width=600 />
<p>
Here you can hit "Disable" to disable the plugin, or "Enable" to activate it, or "Dryrun" to see how it runs in the console log.
If you have installed tkinter, you can click on "Show Config" to see the detail settings.
It's the same thing as you run "movieFromSceneGui.py" directly from file browser or a console. Anyway, you will end up with the screen:
<p>
<img src="https://user-images.githubusercontent.com/22040708/211181257-2182df00-0b8f-4c93-90d9-885dbb0172f6.png" width= 400 />
<p>

* The run mode is obvious: Disable, Enable or just Dry Runs.
* The criteria defines under what condition this plugin will automatically create a movie.

As the above example, it will only create a movie when:

1. The scene has no movies.
2. The scene has title. ( This is not the same as the file name. )
3. The scene has at least one performer.
4. The scene has some details text.

Only when all 4 conditions are met, and after the scene is updated with something, then a movie will be created and linked to that scene. The new movie will try to copy as much information as possible, including title, studio, duration, date, details and front cover. <br>
The new movie will not copy the URL from scene directly, instead it will copy the scene's internal URL, like "http://localhost:9999/scenes/1234" . Because I am planning on create a scraper that will allow you to update the movie information from this URL. It doesn't make much sense to direct-copy the external URL if the scraper cannot scrape it for movies.

### I got a problem with xxx...
This is only version 1.0. So please raise an issue and let me know. I am not a Linux or Docker guy, so please don't expect me to solve
problems related to that. In fact, this is my first time to build a Python GUI program. Please be understanding.



31 changes: 31 additions & 0 deletions plugins/movieFromScene/movieFromScene.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Auto-Create Movie From Scene
description: Automatically create a movie from a scene when scene gets updated. Require Python 3.10 and above. If you use the config GUI, tkinter should be installed.
url: https://github.com/stashapp/CommunityScripts
version: 1.0
exec:
- python
- "{pluginDir}/py_plugins/movieFromScene.py"
interface: raw
hooks:
- name: hook_create_movie_from_scene
description: Create a movie from a scene if it fits certain criteria
triggeredBy:
- Scene.Update.Post
tasks:
- name: 'Disable'
description: Don't auto-create movies. Save this setting in the config file.
defaultArgs:
mode: disable
- name: 'Enable'
description: Auto-Create a movie when scene is updated and fits the criteria. Save this setting in the config file.
defaultArgs:
mode: enable
- name: 'Dry Run'
description: Run but not create any movies. Only show in the log. Save this setting in the config file.
defaultArgs:
mode: dryrun
- name: 'Show Config'
description: Use tkinter to show detailed config GUI for this plugin. Make sure tkinter is installed before this.
defaultArgs:
mode: config

1 change: 1 addition & 0 deletions plugins/movieFromScene/py_plugins/movieFromScene.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"mode": "disable", "criteria": {"no movie": true, "title": true, "URL": false, "date": false, "studio": false, "performer": true, "tag": false, "details": true, "organized": false}}
143 changes: 143 additions & 0 deletions plugins/movieFromScene/py_plugins/movieFromScene.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import json
import os
import sys
import time
import log

try:
import movieFromSceneDef as defs
except Exception:
output_json = {"output":"", "error": 'Import module movieFromSceneDef.py failed.'}
sys.exit

# APP/DB Schema version prior to files refactor PR
# 41 is v0.18.0 (11/30/2022)
API_VERSION_BF_FILES = 41

# check python version
p_version = sys.version
if float( p_version[:p_version.find(".",2)] ) < 3.1 :
defs.exit_plugin("", "Error: You need at least python 3.10 for this plugin.")

# Get data from Stash
FRAGMENT = json.loads(sys.stdin.read())
# log.LogDebug("Fragment:" + json.dumps(FRAGMENT))


FRAGMENT_SERVER = FRAGMENT["server_connection"]
FRAGMENT_SCENE = FRAGMENT["args"].get("hookContext")

# Graphql properties in a dict
g = { "port": FRAGMENT_SERVER['Port'],
"scheme": FRAGMENT_SERVER['Scheme'],
"session": FRAGMENT_SERVER['SessionCookie']['Value'],
"args": FRAGMENT["args"],
"plugin_dir": FRAGMENT_SERVER['PluginDir'],
"dir": FRAGMENT_SERVER['Dir']}
# log.LogDebug("g:" + str(g) )

if FRAGMENT_SERVER['Host'] == "0.0.0.0":
g['host'] = 'localhost'
else:
g['host'] = FRAGMENT_SERVER['Host']

system_status = defs.get_api_version(g)
# log.LogDebug(json.dumps(system_status))

api_version = system_status["appSchema"]

if api_version < API_VERSION_BF_FILES: # Only needed for versions after files refactor
defs.exit_plugin(
f"Stash with API version:{api_version} is not supported. You need at least {API_VERSION_BF_FILES}"
)

# load config file.
configFile = g['plugin_dir']+"/py_plugins/movieFromScene.config"
try:
f = open( configFile,"r" )
config = json.load( f )
f.close()
except FileNotFoundError as e:
# This is the default config, when the config file is missing.
configStr = """
{
"mode": "disable",
"criteria" : {
"no movie" : true,
"title" : true,
"URL" : false,
"date" : false,
"studio" : false,
"performer" : true,
"tag" : false,
"details" : true,
"organized" : false
}
}
"""
f = open( configFile, "w")
f.write(configStr)
f.close()
config = json.loads(configStr)
except:
defs.exit_plugin("","Error in config file: movieFromScene.config. Err:" + str(Exception) )

if not FRAGMENT_SCENE:
match g["args"]["mode"]:
case "config":
# log.LogDebug("run the gui process.")
# Require tkinter module installed in Python.
import subprocess
DETACHED_PROCESS = 0x00000008
g['dir'] = str( g['dir'] ).replace('\\', '/')
guiPy = g['dir'] + '/' + g['plugin_dir']+"/py_plugins/movieFromSceneGui.py"
# log.LogDebug("guiPy:" + guiPy)
subprocess.Popen([sys.executable, guiPy], creationflags=DETACHED_PROCESS)
defs.exit_plugin("Config GUI launched.")

case "disable":
# Disable the plugin and save the setting.
config['mode'] = "disable"
bSuccess = defs.SaveSettings(configFile, config)
if bSuccess:
defs.exit_plugin("Plugin Disabled.")
else:
defs.exit_plugin("Error saving settings.")
# log.LogDebug("hit the disable button.")
case "enable":
config['mode'] = "enable"
bSuccess = defs.SaveSettings(configFile, config)
if bSuccess:
defs.exit_plugin("Plugin Enabled.")
else:
defs.exit_plugin("Error saving settings.")

# log.LogDebug("hit the enable button.")
case "dryrun":
config['mode'] = "disable"
bSuccess = defs.SaveSettings(configFile, config)
if bSuccess:
defs.exit_plugin("Plugin in Dry Run mode.")
else:
defs.exit_plugin("Error saving settings.")

# log.LogDebug("hit the dryrun button.")
case "batch":
log.LogDebug("hit the batch button.")

# The above is for settings run in Tasks.
# The below is for auto movie creation.

if config['mode'] == 'disable':
defs.exit_plugin("Plugin disabled. Not doing anything.")


scene_id = FRAGMENT_SCENE["id"]
if not scene_id:
defs.exit_plugin("", "No Scene ID found!")

SCENE = defs.get_scene( scene_id, g )
# Get the scene info
# log.LogDebug("scene:" + json.dumps(SCENE))

defs.create_Movie_By_Config(SCENE, config, g)
Loading