diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx index e7d7a8b41e3..25d5a395d9d 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerEditPanel.tsx @@ -190,6 +190,69 @@ export const PerformerEditPanel: React.FC = ({ } } + function translateScrapedMeasurements(scrapedMeasurements?: string) { + if (!scrapedMeasurements) { + return; + } + + // Check for autoConvert setting in Settings->Interface->Editing + const autoConvert = stashConfig?.ui.autoConvertMetricMeasurements ?? ''; + if (!autoConvert){ + return scrapedMeasurements + } + + // A lot of JAV sites list measurements as "B90 W80 H90", so if that's there convert it to ##-##-## format + const javMeasurements = scrapedMeasurements.replace(/[^0-9BWH]+/g, '').toUpperCase(); + const javPattern = /^B(\d+)W(\d+)H(\d+)$/; + const javMatch = javMeasurements.match(javPattern); + if (javMatch){ + const javBust = parseInt(javMatch[1]); + const JavWaist = parseInt(javMatch[2]); + const JavHips = parseInt(javMatch[3]); + scrapedMeasurements = `${javBust}-${JavWaist}-${JavHips}`; + } + + // Strip out all invalid characters for measurements + scrapedMeasurements = scrapedMeasurements.replace('/[^0-9A-Z-\/ ]+/g', ''); + + // Define the regex pattern for measurements (e.g., "90D-85-90") + const pattern = /^(\d+)([A-Z]+)?[-\/ ](\d+)[-\/ ](\d+)$/; + const upperMeasurements = scrapedMeasurements.toUpperCase(); + const match = upperMeasurements.match(pattern); + if (!match) { + return upperMeasurements; + } + + // With regex group matches, set values to groups + const bust = parseInt(match[1]); + const cupSize = match[2] || ''; // Cup size is optional + const waist = parseInt(match[3]); + const hips = parseInt(match[4]); + + if (bust > 50 && waist > 45 && hips > 50) { + // Convert centimeters to inches + const cmToInches = (cm: number) => Math.round(cm * 0.393701); + + const bustInches = cmToInches(bust); + const waistInches = cmToInches(waist); + const hipsInches = cmToInches(hips); + + // Convert European cup sizes to US standard + let newCupsize = cupSize; + if (newCupsize) { + const stringPairs = [['A', 'AA'], ['B', 'A'], ['C', 'B'], ['D', 'C'], ['E', 'D'], ['F', 'DD'], ['G', 'E'], ['H', 'F'], ['I', 'G'], ['J', 'H'], ['K', 'I']]; + const matchedPair = stringPairs.find(([firstString]) => firstString === newCupsize); + newCupsize = matchedPair ? matchedPair[1] : newCupsize; + } + + // Create the result string in the same format + const resultString = `${bustInches}${newCupsize}-${waistInches}-${hipsInches}`; + + return resultString; + } + return upperMeasurements; + } + function updatePerformerEditStateFromScraper( state: Partial ) { @@ -221,7 +284,11 @@ export const PerformerEditPanel: React.FC = ({ formik.setFieldValue("height_cm", parseInt(state.height, 10)); } if (state.measurements) { - formik.setFieldValue("measurements", state.measurements); + // measurements is a string in the scraper data + const newMeasurements = translateScrapedMeasurements(state.measurements); + if (newMeasurements) { + formik.setFieldValue("measurements", newMeasurements); + } } if (state.fake_tits) { formik.setFieldValue("fake_tits", state.fake_tits); @@ -623,6 +690,16 @@ export const PerformerEditPanel: React.FC = ({ return renderField("tag_ids", title, tagsControl()); } + function renderMeasurements() { + const newMeasurements = translateScrapedMeasurements(formik.values.measurements); + if (newMeasurements){ + if (formik.values.measurements != newMeasurements){ + formik.setFieldValue("measurements", newMeasurements) + } + } + return renderInputField("measurements"); + } + return ( <> {renderScrapeModal()} @@ -656,7 +733,7 @@ export const PerformerEditPanel: React.FC = ({ {renderSelectField("circumcised", stringCircumMap)} - {renderInputField("measurements")} + {renderMeasurements()} {renderInputField("fake_tits")} {renderInputField("tattoos", "textarea")} diff --git a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx index c174847c982..0c0ad80ca0b 100644 --- a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx @@ -488,8 +488,7 @@ export const SettingsInterfacePanel: React.FC = PatchComponent( ))} - - + )} + saveUI({ autoConvertMetricMeasurements: v })} + /> diff --git a/ui/v2.5/src/core/config.ts b/ui/v2.5/src/core/config.ts index 213d8c11340..5043b12a49a 100644 --- a/ui/v2.5/src/core/config.ts +++ b/ui/v2.5/src/core/config.ts @@ -60,6 +60,8 @@ export interface IUIConfig { enableTagBackgroundImage?: boolean; // if true view expanded details compact compactExpandedDetails?: boolean; + // if true measurements > 50-45-50 will be converted into US (Imperial) values + autoConvertMetricMeasurements?: boolean; // if true show all content details by default showAllDetails?: boolean; diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 59ea1459e17..0379675ee4f 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -642,7 +642,11 @@ "stars": "Stars" } } - } + }, + "auto_convert_metric_measurements": { + "description": "When enabled, this option will auto-convert metric measurements into US (Inches) for values > 50-45-50 (In Performer Edit)", + "heading": "Auto Convert Metric Measurements" + } }, "funscript_offset": { "description": "Time offset in milliseconds for interactive scripts playback.",