Files
ComicRack_CompleteMetadata/Package.py
2026-01-29 17:23:01 +01:00

231 lines
9.6 KiB
Python

import clr
clr.AddReference("System.Windows.Forms")
clr.AddReference("System")
clr.AddReference("System.Web.Extensions")
from System.Windows.Forms import MessageBox
from System.Diagnostics import Process, ProcessStartInfo
from System.Web.Script.Serialization import JavaScriptSerializer
from System.IO import Path, File
from System.Text import UTF8Encoding
serializer = JavaScriptSerializer()
serializer.MaxJsonLength = 2147483647 # Max int32 - handle large book selections
SCRIPT_DIR = Path.GetDirectoryName(__file__)
PYTHON_EXE = r"pythonw.exe"
# ==============================================================================
# @Name Sync: Export to CBZ
# @Hook Books
# @Image export.png
# ==============================================================================
def StartExport(books):
if not books:
return
job = []
for b in books:
# Collect all fields for v2.0 Schema
# Build pages list
pages = []
for p in b.Pages:
pages.append({
"Image": p.ImageIndex,
"ImageWidth": p.ImageWidth,
"ImageHeight": p.ImageHeight,
"Type": str(p.PageType) if str(p.PageType) != "Story" else ""
})
job.append({
"ID": str(b.Id),
"FilePath": b.FilePath,
# v2.0 fields in schema order
"Title": b.Title,
"Series": b.Series,
"Number": b.Number,
"Count": b.Count,
"Volume": b.Volume,
"AlternateSeries": b.AlternateSeries,
"AlternateNumber": b.AlternateNumber,
"AlternateCount": b.AlternateCount if b.AlternateCount > 0 else None,
"Summary": b.Summary,
"Notes": b.Notes,
"Year": b.Year,
"Month": b.Month,
"Day": b.Day,
"Writer": b.Writer,
"Penciller": b.Penciller,
"Inker": b.Inker,
"Colorist": b.Colorist,
"Letterer": b.Letterer,
"CoverArtist": b.CoverArtist,
"Editor": b.Editor,
"Publisher": b.Publisher,
"Imprint": b.Imprint,
"Genre": b.Genre,
"Web": b.Web if hasattr(b, 'Web') else "",
"PageCount": b.PageCount,
"LanguageISO": b.LanguageISO,
"Format": b.Format,
"BlackAndWhite": str(b.BlackAndWhite) if str(b.BlackAndWhite) != "Unknown" else "",
"Manga": str(b.Manga) if str(b.Manga) != "Unknown" else "",
"Characters": b.Characters,
"Teams": b.Teams if hasattr(b, 'Teams') else "",
"Locations": b.Locations,
"ScanInformation": b.ScanInformation if hasattr(b, 'ScanInformation') else "",
"StoryArc": b.StoryArc,
"SeriesGroup": b.SeriesGroup,
"AgeRating": b.AgeRating,
"CommunityRating": b.CommunityRating if b.CommunityRating > 0 else None,
"Rating": b.Rating if hasattr(b, 'Rating') and b.Rating > 0 else None,
"MainCharacterOrTeam": b.MainCharacterOrTeam,
"Review": b.Review if hasattr(b, 'Review') else "",
"Pages": pages,
# CR-specific data
"HasBeenRead": b.HasBeenRead,
"CustomValuesStore": b.CustomValuesStore if b.CustomValuesStore else ""
})
# Save Job to temp file (use .NET for UTF-8 encoding)
temp = Path.Combine(Path.GetTempPath(), 'cr_sync_job.json')
File.WriteAllText(temp, serializer.Serialize(job), UTF8Encoding(False))
# Execute the External Python 3 Worker
script = Path.Combine(SCRIPT_DIR, "cr_sync_worker.py")
# Use .NET Process to launch (IronPython has no subprocess module)
psi = ProcessStartInfo()
psi.FileName = PYTHON_EXE
psi.Arguments = '"%s" export "%s"' % (script, temp)
psi.UseShellExecute = False
psi.CreateNoWindow = True
Process.Start(psi)
# ==============================================================================
# @Name Sync: Read from CBZ
# @Hook Books
# @Image import.png
# ==============================================================================
def StartImport(books):
if not books:
return
job = [{"ID": str(b.Id), "FilePath": b.FilePath} for b in books]
temp = Path.Combine(Path.GetTempPath(), 'cr_read_job.json')
File.WriteAllText(temp, serializer.Serialize(job), UTF8Encoding(False))
script = Path.Combine(SCRIPT_DIR, "cr_sync_worker.py")
psi = ProcessStartInfo()
psi.FileName = PYTHON_EXE
psi.Arguments = '"%s" import "%s"' % (script, temp)
psi.UseShellExecute = False
psi.CreateNoWindow = True
Process.Start(psi)
# ==============================================================================
# @Name Sync: Apply Status
# @Hook Books
# @Image apply.png
# ==============================================================================
def ApplySyncResults(books):
results_path = Path.Combine(SCRIPT_DIR, "sync_results.json")
lock_path = Path.Combine(SCRIPT_DIR, "sync.lock")
# Check if worker is still running
if File.Exists(lock_path):
MessageBox.Show("Sync is still in progress. Please wait for it to finish.")
return
if not File.Exists(results_path):
MessageBox.Show("No sync results found. Run a sync process first!")
return
updates = serializer.DeserializeObject(File.ReadAllText(results_path, UTF8Encoding(False)))
synced_count = 0
skipped_count = 0
failed_count = 0
imported_count = 0
for b in books:
bid = str(b.Id)
if bid not in updates:
continue
data = updates[bid]
# Check export status - only mark as synced if successful
if "status" in data:
status = data["status"]
if status == "success":
b.SetCustomValue("ExtSyncStatus", "Synced")
b.ComicInfoIsDirty = False # Clear "Modified Info" flag
synced_count += 1
elif status == "skipped":
# Skipped means content was identical - already synced
b.SetCustomValue("ExtSyncStatus", "Synced")
b.ComicInfoIsDirty = False # Clear "Modified Info" flag
skipped_count += 1
else:
# Failed - don't mark as synced
failed_count += 1
continue
# If we are in IMPORT mode, the data dict will have metadata tags
elif "Series" in data:
# Apply all standard fields (.NET dict uses ContainsKey, not .get())
if "Title" in data and data["Title"]: b.Title = data["Title"]
if "Series" in data and data["Series"]: b.Series = data["Series"]
if "Number" in data and data["Number"]: b.Number = data["Number"]
if "Volume" in data and data["Volume"]: b.Volume = int(data["Volume"])
if "Summary" in data and data["Summary"]: b.Summary = data["Summary"]
if "Notes" in data and data["Notes"]: b.Notes = data["Notes"]
if "Year" in data and data["Year"]: b.Year = int(data["Year"])
if "Month" in data and data["Month"]: b.Month = int(data["Month"])
if "Day" in data and data["Day"]: b.Day = int(data["Day"])
if "Writer" in data and data["Writer"]: b.Writer = data["Writer"]
if "Penciller" in data and data["Penciller"]: b.Penciller = data["Penciller"]
if "Inker" in data and data["Inker"]: b.Inker = data["Inker"]
if "Colorist" in data and data["Colorist"]: b.Colorist = data["Colorist"]
if "Letterer" in data and data["Letterer"]: b.Letterer = data["Letterer"]
if "CoverArtist" in data and data["CoverArtist"]: b.CoverArtist = data["CoverArtist"]
if "Editor" in data and data["Editor"]: b.Editor = data["Editor"]
if "Publisher" in data and data["Publisher"]: b.Publisher = data["Publisher"]
if "Genre" in data and data["Genre"]: b.Genre = data["Genre"]
if "Format" in data and data["Format"]: b.Format = data["Format"]
if "Characters" in data and data["Characters"]: b.Characters = data["Characters"]
if "Locations" in data and data["Locations"]: b.Locations = data["Locations"]
if "StoryArc" in data and data["StoryArc"]: b.StoryArc = data["StoryArc"]
if "SeriesGroup" in data and data["SeriesGroup"]: b.SeriesGroup = data["SeriesGroup"]
if "AgeRating" in data and data["AgeRating"]: b.AgeRating = data["AgeRating"]
if "MainCharacterOrTeam" in data and data["MainCharacterOrTeam"]: b.MainCharacterOrTeam = data["MainCharacterOrTeam"]
# Apply HasBeenRead (now a standard element, already converted to boolean)
if "HasBeenRead" in data:
b.HasBeenRead = data["HasBeenRead"]
# Apply other custom values
if "CustomValues" in data:
custom_vals = data["CustomValues"]
for key in custom_vals.Keys:
b.SetCustomValue(key, custom_vals[key])
b.SetCustomValue("ExtSyncStatus", "Synced")
imported_count += 1
# Cleanup results file after applying
try:
File.Delete(results_path)
except:
pass
# Build summary message
msg_parts = []
if synced_count > 0:
msg_parts.append(str(synced_count) + " exported")
if skipped_count > 0:
msg_parts.append(str(skipped_count) + " unchanged")
if imported_count > 0:
msg_parts.append(str(imported_count) + " imported")
if failed_count > 0:
msg_parts.append(str(failed_count) + " FAILED (check sync_errors.log)")
MessageBox.Show(", ".join(msg_parts) if msg_parts else "No matching books found.")