1+ import stashapi .log as log
2+ from stashapi .stashapp import StashInterface
3+ import stashapi .marker_parse as mp
4+ import sys
5+ import json
6+ import time
7+ from pathlib import Path
8+ import subprocess
9+ import re
10+
11+ def extract_chapters_from_video (video_path ):
12+ try :
13+ cmd = [
14+ "ffprobe" ,
15+ "-v" , "quiet" ,
16+ "-print_format" , "json" ,
17+ "-show_chapters" ,
18+ video_path
19+ ]
20+ result = subprocess .run (cmd , capture_output = True , text = True )
21+
22+ if result .returncode != 0 :
23+ log .error (f"Error extracting chapters from { video_path } : { result .stderr } " )
24+ return []
25+
26+ chapters_data = json .loads (result .stdout )
27+
28+ if "chapters" not in chapters_data or len (chapters_data ["chapters" ]) == 0 :
29+ log .debug (f"No chapters found in { video_path } " )
30+ return []
31+
32+ markers = []
33+ for chapter in chapters_data ["chapters" ]:
34+ title = chapter .get ("tags" , {}).get ("title" , "Chapter" )
35+
36+ start_time = float (chapter .get ("start_time" , 0 ))
37+
38+ marker = {
39+ "seconds" : start_time ,
40+ "primary_tag" : "From Chapter" ,
41+ "tags" : [],
42+ "title" : title ,
43+ }
44+ log .debug (marker )
45+ markers .append (marker )
46+
47+ return markers
48+
49+ except Exception as e :
50+ log .error (f"Error processing { video_path } : { str (e )} " )
51+ return []
52+
53+ def processScene (scene ):
54+ log .debug (scene ["scene_markers" ])
55+ # Only process scenes without existing markers
56+ if len (scene ["scene_markers" ]) == 0 :
57+ for f in scene ["files" ]:
58+ video_path = f ["path" ]
59+ log .debug (f"Processing video: { video_path } " )
60+
61+ markers = extract_chapters_from_video (video_path )
62+
63+ if len (markers ) > 0 :
64+ if len (markers ) == 1 and markers [0 ]["seconds" ] == 0 :
65+ log .info (f"Single chapter at the beginning in { video_path } , skipping import." )
66+ else :
67+ log .info (f"Found { len (markers )} chapters in { video_path } " )
68+ mp .import_scene_markers (stash , markers , scene ["id" ], 15 )
69+ else :
70+ log .info (f"No chapters found in { video_path } " )
71+
72+ def processAll ():
73+ query = {
74+ "has_markers" : "false" ,
75+ }
76+ per_page = 100
77+ log .info ("Getting scene count" )
78+ count = stash .find_scenes (
79+ f = query ,
80+ filter = {"per_page" : 1 },
81+ get_count = True ,
82+ )[0 ]
83+ log .info (str (count ) + " scenes to process." )
84+
85+ for r in range (1 , int (count / per_page ) + 2 ):
86+ i = (r - 1 ) * per_page
87+ log .info (
88+ "fetching data: %s - %s %0.1f%%"
89+ % (
90+ (r - 1 ) * per_page ,
91+ r * per_page ,
92+ (i / count ) * 100 ,
93+ )
94+ )
95+ scenes = stash .find_scenes (
96+ f = query ,
97+ filter = {"page" : r , "per_page" : per_page },
98+ )
99+ for s in scenes :
100+ processScene (s )
101+ i = i + 1
102+ log .progress ((i / count ))
103+
104+ json_input = json .loads (sys .stdin .read ())
105+
106+ FRAGMENT_SERVER = json_input ["server_connection" ]
107+ stash = StashInterface (FRAGMENT_SERVER )
108+
109+ if "mode" in json_input ["args" ]:
110+ PLUGIN_ARGS = json_input ["args" ]["mode" ]
111+ if "processAll" == PLUGIN_ARGS :
112+ processAll ()
113+ elif "hookContext" in json_input ["args" ]:
114+ _id = json_input ["args" ]["hookContext" ]["id" ]
115+ _type = json_input ["args" ]["hookContext" ]["type" ]
116+ if _type == "Scene.Update.Post" :
117+ scene = stash .find_scene (_id )
118+ processScene (scene )
0 commit comments