retold-remote 0.0.15 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -9
- package/source/Pict-Application-RetoldRemote.js +5 -2
- package/source/cli/RetoldRemote-Server-Setup.js +46 -2
- package/source/cli/commands/RetoldRemote-Command-Serve.js +65 -1
- package/source/server/RetoldRemote-AudioWaveformService.js +13 -14
- package/source/server/RetoldRemote-EbookService.js +7 -13
- package/source/server/RetoldRemote-ImageService.js +145 -32
- package/source/server/RetoldRemote-MediaService.js +134 -67
- package/source/server/RetoldRemote-UltravisorBeacon.js +175 -0
- package/source/server/RetoldRemote-UltravisorDispatcher.js +462 -86
- package/source/server/RetoldRemote-UltravisorOperations.js +481 -0
- package/source/server/RetoldRemote-VideoFrameService.js +20 -26
- package/source/views/PictView-Remote-TopBar.js +56 -67
- package/web-application/js/pict.min.js +2 -2
- package/web-application/js/pict.min.js.map +1 -1
- package/web-application/retold-remote.js +4731 -4248
- package/web-application/retold-remote.js.map +1 -1
- package/web-application/retold-remote.min.js +16 -16
- package/web-application/retold-remote.min.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "retold-remote",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Retold Remote - NAS media browser with gallery views and keyboard navigation",
|
|
5
5
|
"main": "source/Pict-RetoldRemote-Bundle.js",
|
|
6
6
|
"bin": {
|
|
@@ -34,31 +34,32 @@
|
|
|
34
34
|
"dcraw": "^1.0.3",
|
|
35
35
|
"epubjs": "^0.3.93",
|
|
36
36
|
"exifr": "^7.1.3",
|
|
37
|
-
"fable": "^3.1.
|
|
37
|
+
"fable": "^3.1.67",
|
|
38
38
|
"fable-serviceproviderbase": "^3.0.19",
|
|
39
39
|
"node-unrar-js": "^2.0.2",
|
|
40
40
|
"orator": "^6.0.4",
|
|
41
41
|
"orator-serviceserver-restify": "^2.0.9",
|
|
42
42
|
"parime": "^1.0.3",
|
|
43
|
-
"pdf-parse": "^
|
|
44
|
-
"pict": "^1.0.
|
|
43
|
+
"pdf-parse": "^2.4.5",
|
|
44
|
+
"pict": "^1.0.359",
|
|
45
45
|
"pict-application": "^1.0.33",
|
|
46
46
|
"pict-docuserve": "^0.0.32",
|
|
47
47
|
"pict-provider": "^1.0.12",
|
|
48
|
-
"pict-section-code": "^1.0.
|
|
48
|
+
"pict-section-code": "^1.0.4",
|
|
49
49
|
"pict-section-filebrowser": "^0.0.2",
|
|
50
50
|
"pict-service-commandlineutility": "^1.0.19",
|
|
51
51
|
"pict-view": "^1.0.67",
|
|
52
|
-
"retold-content-system": "^1.0.
|
|
53
|
-
"
|
|
52
|
+
"retold-content-system": "^1.0.12",
|
|
53
|
+
"ultravisor-beacon": "^0.0.4",
|
|
54
|
+
"yauzl": "^3.2.1"
|
|
54
55
|
},
|
|
55
56
|
"optionalDependencies": {
|
|
56
57
|
"@img/sharp-wasm32": "^0.34.5",
|
|
57
58
|
"sharp": "^0.34.5"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
60
|
-
"puppeteer": "^24.
|
|
61
|
-
"quackage": "^1.0.
|
|
61
|
+
"puppeteer": "^24.40.0",
|
|
62
|
+
"quackage": "^1.0.65"
|
|
62
63
|
},
|
|
63
64
|
"copyFilesSettings": {
|
|
64
65
|
"whenFileExists": "overwrite"
|
|
@@ -721,12 +721,15 @@ class RetoldRemoteApplication extends libContentEditorApplication
|
|
|
721
721
|
*/
|
|
722
722
|
_executeRoute(pRoute, pPath)
|
|
723
723
|
{
|
|
724
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
725
|
+
let tmpActiveMode = tmpRemote.ActiveMode || 'gallery';
|
|
726
|
+
|
|
724
727
|
switch (pRoute)
|
|
725
728
|
{
|
|
726
729
|
case 'browse':
|
|
727
730
|
{
|
|
728
731
|
let tmpCurrentPath = (this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation) || '';
|
|
729
|
-
if (pPath !== tmpCurrentPath)
|
|
732
|
+
if (pPath !== tmpCurrentPath || tmpActiveMode !== 'gallery')
|
|
730
733
|
{
|
|
731
734
|
this.loadFileList(pPath);
|
|
732
735
|
}
|
|
@@ -735,7 +738,7 @@ class RetoldRemoteApplication extends libContentEditorApplication
|
|
|
735
738
|
case 'view':
|
|
736
739
|
case 'edit':
|
|
737
740
|
{
|
|
738
|
-
if (this.pict.AppData.ContentEditor.CurrentFile !== pPath)
|
|
741
|
+
if (this.pict.AppData.ContentEditor.CurrentFile !== pPath || tmpActiveMode === 'gallery')
|
|
739
742
|
{
|
|
740
743
|
this.navigateToFile(pPath);
|
|
741
744
|
}
|
|
@@ -49,6 +49,7 @@ const libRetoldRemoteFileOperationService = require('../server/RetoldRemote-File
|
|
|
49
49
|
const libRetoldRemoteAISortService = require('../server/RetoldRemote-AISortService.js');
|
|
50
50
|
const libRetoldRemoteImageService = require('../server/RetoldRemote-ImageService.js');
|
|
51
51
|
const libRetoldRemoteUltravisorDispatcher = require('../server/RetoldRemote-UltravisorDispatcher.js');
|
|
52
|
+
const libRetoldRemoteUltravisorBeacon = require('../server/RetoldRemote-UltravisorBeacon.js');
|
|
52
53
|
const libUrl = require('url');
|
|
53
54
|
|
|
54
55
|
function setupRetoldRemoteServer(pOptions, fCallback)
|
|
@@ -88,6 +89,13 @@ function setupRetoldRemoteServer(pOptions, fCallback)
|
|
|
88
89
|
if (pOptions.UltravisorURL)
|
|
89
90
|
{
|
|
90
91
|
tmpSettings.UltravisorURL = pOptions.UltravisorURL;
|
|
92
|
+
|
|
93
|
+
// Auto-construct ContentAPIURL from the server's own port so beacons
|
|
94
|
+
// know where to download source files from retold-remote.
|
|
95
|
+
if (!pOptions.ContentAPIURL)
|
|
96
|
+
{
|
|
97
|
+
tmpSettings.ContentAPIURL = 'http://localhost:' + pOptions.Port;
|
|
98
|
+
}
|
|
91
99
|
}
|
|
92
100
|
if (pOptions.ContentAPIURL)
|
|
93
101
|
{
|
|
@@ -215,6 +223,9 @@ function setupRetoldRemoteServer(pOptions, fCallback)
|
|
|
215
223
|
// Set up the Ultravisor dispatcher for offloading heavy processing
|
|
216
224
|
let tmpDispatcher = new libRetoldRemoteUltravisorDispatcher(tmpFable, {});
|
|
217
225
|
|
|
226
|
+
// Set up the Ultravisor beacon for mesh registration
|
|
227
|
+
let tmpBeacon = new libRetoldRemoteUltravisorBeacon(tmpFable, {});
|
|
228
|
+
|
|
218
229
|
// Wire the dispatcher to services that can offload processing
|
|
219
230
|
tmpMediaService.setDispatcher(tmpDispatcher);
|
|
220
231
|
tmpVideoFrameService.setDispatcher(tmpDispatcher);
|
|
@@ -2127,7 +2138,7 @@ function setupRetoldRemoteServer(pOptions, fCallback)
|
|
|
2127
2138
|
tmpOrator.startService(
|
|
2128
2139
|
function ()
|
|
2129
2140
|
{
|
|
2130
|
-
|
|
2141
|
+
let tmpServerInfo =
|
|
2131
2142
|
{
|
|
2132
2143
|
Fable: tmpFable,
|
|
2133
2144
|
Orator: tmpOrator,
|
|
@@ -2140,8 +2151,41 @@ function setupRetoldRemoteServer(pOptions, fCallback)
|
|
|
2140
2151
|
MetadataCache: tmpMetadataCache,
|
|
2141
2152
|
FileOperationService: tmpFileOperationService,
|
|
2142
2153
|
UltravisorDispatcher: tmpDispatcher,
|
|
2154
|
+
UltravisorBeacon: tmpBeacon,
|
|
2143
2155
|
Port: tmpPort
|
|
2144
|
-
}
|
|
2156
|
+
};
|
|
2157
|
+
|
|
2158
|
+
// If Ultravisor URL is configured, connect as a beacon
|
|
2159
|
+
if (pOptions.UltravisorURL)
|
|
2160
|
+
{
|
|
2161
|
+
let tmpContentAPIBase = tmpFable.settings.ContentAPIURL || ('http://localhost:' + tmpPort);
|
|
2162
|
+
let tmpContentBaseURL = tmpContentAPIBase + '/content/';
|
|
2163
|
+
|
|
2164
|
+
tmpBeacon.connectBeacon(
|
|
2165
|
+
{
|
|
2166
|
+
ServerURL: pOptions.UltravisorURL,
|
|
2167
|
+
Name: 'retold-remote',
|
|
2168
|
+
ContentPath: tmpContentPath,
|
|
2169
|
+
ContentBaseURL: tmpContentBaseURL,
|
|
2170
|
+
CacheRoot: tmpCacheRoot,
|
|
2171
|
+
StagingPath: tmpCacheRoot || process.cwd(),
|
|
2172
|
+
BindAddresses: [{ IP: '127.0.0.1', Port: tmpPort, Protocol: 'http' }]
|
|
2173
|
+
},
|
|
2174
|
+
(pBeaconError) =>
|
|
2175
|
+
{
|
|
2176
|
+
if (pBeaconError)
|
|
2177
|
+
{
|
|
2178
|
+
tmpFable.log.warn(`Ultravisor Beacon: registration failed (server may not be running): ${pBeaconError.message}`);
|
|
2179
|
+
tmpFable.log.warn('Ultravisor Beacon: server is still running. Beacon will not be active.');
|
|
2180
|
+
}
|
|
2181
|
+
// Non-fatal — return server info regardless
|
|
2182
|
+
return fCallback(null, tmpServerInfo);
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
else
|
|
2186
|
+
{
|
|
2187
|
+
return fCallback(null, tmpServerInfo);
|
|
2188
|
+
}
|
|
2145
2189
|
});
|
|
2146
2190
|
});
|
|
2147
2191
|
});
|
|
@@ -26,11 +26,37 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
|
|
|
26
26
|
this.options.CommandOptions.push(
|
|
27
27
|
{ Name: '--cache-server [url]', Description: 'URL of a remote parime cache server (e.g. http://host:9999).', Default: '' });
|
|
28
28
|
|
|
29
|
+
this.options.CommandOptions.push(
|
|
30
|
+
{ Name: '-u, --ultravisor [url]', Description: 'Connect to Ultravisor mesh. URL defaults to http://localhost:54321 if omitted.', Default: '' });
|
|
31
|
+
|
|
32
|
+
this.options.CommandOptions.push(
|
|
33
|
+
{ Name: '-l, --logfile [path]', Description: 'Write logs to a file (auto-generates timestamped name if path omitted).', Default: '' });
|
|
34
|
+
|
|
29
35
|
this.addCommand();
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
onRunAsync(fCallback)
|
|
33
39
|
{
|
|
40
|
+
// Set up file logging if -l was provided
|
|
41
|
+
let tmpLogfileOpt = this.CommandOptions.logfile;
|
|
42
|
+
if (tmpLogfileOpt)
|
|
43
|
+
{
|
|
44
|
+
let tmpLogfilePath;
|
|
45
|
+
if (typeof tmpLogfileOpt === 'string' && tmpLogfileOpt.length > 0)
|
|
46
|
+
{
|
|
47
|
+
tmpLogfilePath = libPath.resolve(tmpLogfileOpt);
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
{
|
|
51
|
+
tmpLogfilePath = libPath.resolve(`retold-remote-${new Date().toISOString().replace(/[:.]/g, '-')}.log`);
|
|
52
|
+
}
|
|
53
|
+
let tmpStreamDef = { loggertype: 'simpleflatfile', level: 'info', path: tmpLogfilePath, outputloglinestoconsole: false, outputobjectstoconsole: false };
|
|
54
|
+
let tmpFileLogger = new this.fable.log._Providers.simpleflatfile(tmpStreamDef, this.fable.log);
|
|
55
|
+
tmpFileLogger.initialize();
|
|
56
|
+
this.fable.log.addLogger(tmpFileLogger, 'info');
|
|
57
|
+
this.log.info(`Logging to file: ${tmpLogfilePath}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
34
60
|
let tmpContentPath = libPath.resolve(this.ArgumentString || process.cwd());
|
|
35
61
|
|
|
36
62
|
let tmpDistPath = libPath.resolve(__dirname, '..', '..', '..', 'web-application');
|
|
@@ -60,6 +86,17 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
|
|
|
60
86
|
? libPath.resolve(this.CommandOptions.cachePath)
|
|
61
87
|
: null;
|
|
62
88
|
let tmpCacheServer = this.CommandOptions.cacheServer || null;
|
|
89
|
+
// -u with no URL → true (Commander behavior for [optional]), default to localhost
|
|
90
|
+
let tmpUltravisorOpt = this.CommandOptions.ultravisor;
|
|
91
|
+
let tmpUltravisorURL = null;
|
|
92
|
+
if (tmpUltravisorOpt === true)
|
|
93
|
+
{
|
|
94
|
+
tmpUltravisorURL = 'http://localhost:54321';
|
|
95
|
+
}
|
|
96
|
+
else if (typeof tmpUltravisorOpt === 'string' && tmpUltravisorOpt.length > 0)
|
|
97
|
+
{
|
|
98
|
+
tmpUltravisorURL = tmpUltravisorOpt;
|
|
99
|
+
}
|
|
63
100
|
|
|
64
101
|
tmpSetupServer(
|
|
65
102
|
{
|
|
@@ -68,7 +105,8 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
|
|
|
68
105
|
Port: tmpPort,
|
|
69
106
|
HashedFilenames: tmpHashedFilenames,
|
|
70
107
|
CacheRoot: tmpCacheRoot,
|
|
71
|
-
CacheServer: tmpCacheServer
|
|
108
|
+
CacheServer: tmpCacheServer,
|
|
109
|
+
UltravisorURL: tmpUltravisorURL
|
|
72
110
|
},
|
|
73
111
|
function (pError, pServerInfo)
|
|
74
112
|
{
|
|
@@ -85,11 +123,37 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
|
|
|
85
123
|
tmpSelf.log.info(` Content: ${tmpContentPath}`);
|
|
86
124
|
tmpSelf.log.info(` Assets: ${tmpDistPath}`);
|
|
87
125
|
tmpSelf.log.info(` Browse: http://localhost:${pServerInfo.Port}/`);
|
|
126
|
+
if (pServerInfo.UltravisorBeacon && pServerInfo.UltravisorBeacon.isEnabled())
|
|
127
|
+
{
|
|
128
|
+
tmpSelf.log.info(` Beacon: registered with Ultravisor at ${tmpUltravisorURL}`);
|
|
129
|
+
}
|
|
130
|
+
else if (tmpUltravisorURL)
|
|
131
|
+
{
|
|
132
|
+
tmpSelf.log.info(` Beacon: not connected (Ultravisor may be unreachable)`);
|
|
133
|
+
}
|
|
88
134
|
tmpSelf.log.info('==========================================================');
|
|
89
135
|
tmpSelf.log.info('');
|
|
90
136
|
tmpSelf.log.info(' Press Ctrl+C to stop.');
|
|
91
137
|
tmpSelf.log.info('');
|
|
92
138
|
|
|
139
|
+
// Graceful shutdown: disconnect beacon before exit
|
|
140
|
+
process.on('SIGINT', () =>
|
|
141
|
+
{
|
|
142
|
+
tmpSelf.log.info('');
|
|
143
|
+
tmpSelf.log.info('Shutting down...');
|
|
144
|
+
if (pServerInfo.UltravisorBeacon && pServerInfo.UltravisorBeacon.isEnabled())
|
|
145
|
+
{
|
|
146
|
+
pServerInfo.UltravisorBeacon.disconnectBeacon(() =>
|
|
147
|
+
{
|
|
148
|
+
process.exit(0);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else
|
|
152
|
+
{
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
93
157
|
// Intentionally do NOT call fCallback() here.
|
|
94
158
|
// The server should keep running.
|
|
95
159
|
});
|
|
@@ -136,7 +136,7 @@ class RetoldRemoteAudioWaveformService extends libFableServiceProviderBase
|
|
|
136
136
|
{
|
|
137
137
|
let tmpSelf = this;
|
|
138
138
|
|
|
139
|
-
// Try Ultravisor
|
|
139
|
+
// Try Ultravisor operation trigger first
|
|
140
140
|
if (this._dispatcher && this._dispatcher.isAvailable())
|
|
141
141
|
{
|
|
142
142
|
let tmpRelPath;
|
|
@@ -151,25 +151,24 @@ class RetoldRemoteAudioWaveformService extends libFableServiceProviderBase
|
|
|
151
151
|
|
|
152
152
|
if (tmpRelPath && !tmpRelPath.startsWith('..'))
|
|
153
153
|
{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this._dispatcher.dispatchMediaCommand(
|
|
154
|
+
this._dispatcher.triggerOperation('rr-media-probe',
|
|
157
155
|
{
|
|
158
|
-
|
|
159
|
-
InputPath: tmpRelPath,
|
|
160
|
-
AffinityKey: tmpRelPath,
|
|
161
|
-
TimeoutMs: 30000
|
|
156
|
+
MediaAddress: '>retold-remote/File/' + tmpRelPath
|
|
162
157
|
},
|
|
163
|
-
(
|
|
158
|
+
(pTriggerError, pResult) =>
|
|
164
159
|
{
|
|
165
|
-
if (!
|
|
160
|
+
if (!pTriggerError && pResult && pResult.TaskOutputs)
|
|
166
161
|
{
|
|
167
162
|
try
|
|
168
163
|
{
|
|
169
|
-
let
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
164
|
+
let tmpProcessOutput = pResult.TaskOutputs['rr-media-probe-process'];
|
|
165
|
+
if (tmpProcessOutput && tmpProcessOutput.Result)
|
|
166
|
+
{
|
|
167
|
+
let tmpData = JSON.parse(tmpProcessOutput.Result);
|
|
168
|
+
let tmpParsed = tmpSelf._parseAudioProbeData(tmpData);
|
|
169
|
+
tmpSelf.fable.log.info(`ffprobe (audio) via operation trigger for ${tmpRelPath}`);
|
|
170
|
+
return fCallback(null, tmpParsed);
|
|
171
|
+
}
|
|
173
172
|
}
|
|
174
173
|
catch (pParseError)
|
|
175
174
|
{
|
|
@@ -192,7 +192,7 @@ class RetoldRemoteEbookService extends libFableServiceProviderBase
|
|
|
192
192
|
return fCallback(null, tmpResult);
|
|
193
193
|
};
|
|
194
194
|
|
|
195
|
-
// Try Ultravisor
|
|
195
|
+
// Try Ultravisor operation trigger first
|
|
196
196
|
if (this._dispatcher && this._dispatcher.isAvailable())
|
|
197
197
|
{
|
|
198
198
|
let tmpRelPath;
|
|
@@ -207,24 +207,18 @@ class RetoldRemoteEbookService extends libFableServiceProviderBase
|
|
|
207
207
|
|
|
208
208
|
if (tmpRelPath && !tmpRelPath.startsWith('..'))
|
|
209
209
|
{
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
this._dispatcher.dispatchMediaCommand(
|
|
210
|
+
this._dispatcher.triggerOperation('rr-ebook-convert',
|
|
213
211
|
{
|
|
214
|
-
|
|
215
|
-
InputPath: tmpRelPath,
|
|
216
|
-
OutputFilename: tmpOutputFilename,
|
|
217
|
-
AffinityKey: tmpRelPath,
|
|
218
|
-
TimeoutMs: 180000
|
|
212
|
+
EbookAddress: '>retold-remote/File/' + tmpRelPath
|
|
219
213
|
},
|
|
220
|
-
(
|
|
214
|
+
(pTriggerError, pResult) =>
|
|
221
215
|
{
|
|
222
|
-
if (!
|
|
216
|
+
if (!pTriggerError && pResult && pResult.OutputBuffer)
|
|
223
217
|
{
|
|
224
218
|
try
|
|
225
219
|
{
|
|
226
220
|
libFs.writeFileSync(tmpOutputPath, pResult.OutputBuffer);
|
|
227
|
-
tmpSelf.fable.log.info(`Ebook converted via
|
|
221
|
+
tmpSelf.fable.log.info(`Ebook converted via operation trigger for ${tmpRelPath}`);
|
|
228
222
|
return _finishConversion(tmpOutputPath, tmpOutputFilename, tmpCacheDir, tmpManifestPath);
|
|
229
223
|
}
|
|
230
224
|
catch (pWriteError)
|
|
@@ -234,7 +228,7 @@ class RetoldRemoteEbookService extends libFableServiceProviderBase
|
|
|
234
228
|
}
|
|
235
229
|
|
|
236
230
|
// Fall through to local processing
|
|
237
|
-
tmpSelf.fable.log.info(`
|
|
231
|
+
tmpSelf.fable.log.info(`Operation trigger failed for ebook conversion, falling back to local: ${pTriggerError ? pTriggerError.message : 'no output'}`);
|
|
238
232
|
tmpSelf._convertToEpubLocal(pAbsPath, tmpOutputPath, tmpOutputFilename, tmpCacheDir, tmpManifestPath, pRelPath, fCallback);
|
|
239
233
|
});
|
|
240
234
|
return;
|
|
@@ -361,7 +361,7 @@ class RetoldRemoteImageService extends libFableServiceProviderBase
|
|
|
361
361
|
{
|
|
362
362
|
let tmpSelf = this;
|
|
363
363
|
|
|
364
|
-
// Try Ultravisor
|
|
364
|
+
// Try Ultravisor operation trigger first
|
|
365
365
|
if (this._dispatcher && this._dispatcher.isAvailable())
|
|
366
366
|
{
|
|
367
367
|
let tmpRelPath;
|
|
@@ -376,21 +376,15 @@ class RetoldRemoteImageService extends libFableServiceProviderBase
|
|
|
376
376
|
|
|
377
377
|
if (tmpRelPath && !tmpRelPath.startsWith('..'))
|
|
378
378
|
{
|
|
379
|
-
|
|
380
|
-
let tmpOutputFilename = libPath.basename(pOutputPath);
|
|
381
|
-
let tmpCommand = `dcraw -c -w${tmpHalfFlag} "{SourcePath}" | convert ppm:- jpeg:"{OutputPath}"`;
|
|
382
|
-
|
|
383
|
-
this._dispatcher.dispatchMediaCommand(
|
|
379
|
+
this._dispatcher.triggerOperation('rr-image-convert',
|
|
384
380
|
{
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
AffinityKey: tmpRelPath,
|
|
389
|
-
TimeoutMs: 180000
|
|
381
|
+
ImageAddress: '>retold-remote/File/' + tmpRelPath,
|
|
382
|
+
Format: 'jpeg',
|
|
383
|
+
Quality: 92
|
|
390
384
|
},
|
|
391
|
-
(
|
|
385
|
+
(pTriggerError, pResult) =>
|
|
392
386
|
{
|
|
393
|
-
if (!
|
|
387
|
+
if (!pTriggerError && pResult && pResult.OutputBuffer)
|
|
394
388
|
{
|
|
395
389
|
try
|
|
396
390
|
{
|
|
@@ -400,7 +394,7 @@ class RetoldRemoteImageService extends libFableServiceProviderBase
|
|
|
400
394
|
libFs.mkdirSync(tmpDir, { recursive: true });
|
|
401
395
|
}
|
|
402
396
|
libFs.writeFileSync(pOutputPath, pResult.OutputBuffer);
|
|
403
|
-
tmpSelf.fable.log.info(`Raw conversion via
|
|
397
|
+
tmpSelf.fable.log.info(`Raw conversion via operation trigger (dcraw) for ${tmpRelPath}`);
|
|
404
398
|
return fCallback(null);
|
|
405
399
|
}
|
|
406
400
|
catch (pWriteError)
|
|
@@ -503,7 +497,7 @@ class RetoldRemoteImageService extends libFableServiceProviderBase
|
|
|
503
497
|
{
|
|
504
498
|
let tmpSelf = this;
|
|
505
499
|
|
|
506
|
-
// Try Ultravisor
|
|
500
|
+
// Try Ultravisor operation trigger first
|
|
507
501
|
if (this._dispatcher && this._dispatcher.isAvailable())
|
|
508
502
|
{
|
|
509
503
|
let tmpRelPath;
|
|
@@ -518,20 +512,15 @@ class RetoldRemoteImageService extends libFableServiceProviderBase
|
|
|
518
512
|
|
|
519
513
|
if (tmpRelPath && !tmpRelPath.startsWith('..'))
|
|
520
514
|
{
|
|
521
|
-
|
|
522
|
-
let tmpCommand = `convert "{SourcePath}" -auto-orient -quality 92 "{OutputPath}"`;
|
|
523
|
-
|
|
524
|
-
this._dispatcher.dispatchMediaCommand(
|
|
515
|
+
this._dispatcher.triggerOperation('rr-image-convert',
|
|
525
516
|
{
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
AffinityKey: tmpRelPath,
|
|
530
|
-
TimeoutMs: 180000
|
|
517
|
+
ImageAddress: '>retold-remote/File/' + tmpRelPath,
|
|
518
|
+
Format: 'jpeg',
|
|
519
|
+
Quality: 92
|
|
531
520
|
},
|
|
532
|
-
(
|
|
521
|
+
(pTriggerError, pResult) =>
|
|
533
522
|
{
|
|
534
|
-
if (!
|
|
523
|
+
if (!pTriggerError && pResult && pResult.OutputBuffer)
|
|
535
524
|
{
|
|
536
525
|
try
|
|
537
526
|
{
|
|
@@ -541,7 +530,7 @@ class RetoldRemoteImageService extends libFableServiceProviderBase
|
|
|
541
530
|
libFs.mkdirSync(tmpDir, { recursive: true });
|
|
542
531
|
}
|
|
543
532
|
libFs.writeFileSync(pOutputPath, pResult.OutputBuffer);
|
|
544
|
-
tmpSelf.fable.log.info(`Raw conversion via
|
|
533
|
+
tmpSelf.fable.log.info(`Raw conversion via operation trigger (ImageMagick) for ${tmpRelPath}`);
|
|
545
534
|
return fCallback(null);
|
|
546
535
|
}
|
|
547
536
|
catch (pWriteError)
|
|
@@ -752,7 +741,11 @@ class RetoldRemoteImageService extends libFableServiceProviderBase
|
|
|
752
741
|
|
|
753
742
|
if (!this._sharp && !this._capabilities.imagemagick)
|
|
754
743
|
{
|
|
755
|
-
|
|
744
|
+
// Neither Sharp nor ImageMagick available locally — check for Ultravisor
|
|
745
|
+
if (!(this._dispatcher && this._dispatcher.isAvailable()))
|
|
746
|
+
{
|
|
747
|
+
return fCallback(new Error('Neither sharp nor ImageMagick is available.'));
|
|
748
|
+
}
|
|
756
749
|
}
|
|
757
750
|
|
|
758
751
|
let tmpMaxDim = pMaxDimension || this.options.DefaultMaxPreviewDimension;
|
|
@@ -822,14 +815,44 @@ class RetoldRemoteImageService extends libFableServiceProviderBase
|
|
|
822
815
|
}
|
|
823
816
|
});
|
|
824
817
|
}
|
|
825
|
-
else if (this.
|
|
818
|
+
else if (this._dispatcher && this._dispatcher.isAvailable())
|
|
819
|
+
{
|
|
820
|
+
// Prefer Ultravisor dispatch when a beacon is available
|
|
821
|
+
this._doGeneratePreviewWithDispatcher(pAbsPath, pRelPath, tmpMaxDim, tmpCacheKey, tmpOutputFilename, tmpCacheDir, tmpManifestPath, tmpOutputPath, tmpStat, tmpIsRaw,
|
|
822
|
+
(pDispatchError, pResult) =>
|
|
823
|
+
{
|
|
824
|
+
if (!pDispatchError && pResult)
|
|
825
|
+
{
|
|
826
|
+
return fCallback(null, pResult);
|
|
827
|
+
}
|
|
828
|
+
// Dispatch failed — fall through to local tools
|
|
829
|
+
tmpSelf.fable.log.info(`Ultravisor dispatch failed for preview, falling back to local: ${pDispatchError ? pDispatchError.message : 'no result'}`);
|
|
830
|
+
tmpSelf._generatePreviewLocal(pAbsPath, pRelPath, tmpMaxDim, tmpCacheKey, tmpOutputFilename, tmpCacheDir, tmpManifestPath, tmpOutputPath, tmpStat, fCallback);
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
else
|
|
834
|
+
{
|
|
835
|
+
this._generatePreviewLocal(pAbsPath, pRelPath, tmpMaxDim, tmpCacheKey, tmpOutputFilename, tmpCacheDir, tmpManifestPath, tmpOutputPath, tmpStat, fCallback);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Generate a preview using local tools (Sharp or ImageMagick).
|
|
841
|
+
* Called when Ultravisor dispatch is unavailable or fails.
|
|
842
|
+
*/
|
|
843
|
+
_generatePreviewLocal(pAbsPath, pRelPath, pMaxDim, pCacheKey, pOutputFilename, pCacheDir, pManifestPath, pOutputPath, pStat, fCallback)
|
|
844
|
+
{
|
|
845
|
+
if (this._sharp)
|
|
846
|
+
{
|
|
847
|
+
this._doGeneratePreview(pAbsPath, pAbsPath, pRelPath, pMaxDim, pCacheKey, pOutputFilename, pCacheDir, pManifestPath, pOutputPath, pStat, false, fCallback);
|
|
848
|
+
}
|
|
849
|
+
else if (this._capabilities.imagemagick)
|
|
826
850
|
{
|
|
827
|
-
this.
|
|
851
|
+
this._doGeneratePreviewWithImageMagick(pAbsPath, pRelPath, pMaxDim, pCacheKey, pOutputFilename, pManifestPath, pOutputPath, pStat, false, fCallback);
|
|
828
852
|
}
|
|
829
853
|
else
|
|
830
854
|
{
|
|
831
|
-
|
|
832
|
-
this._doGeneratePreviewWithImageMagick(pAbsPath, pRelPath, tmpMaxDim, tmpCacheKey, tmpOutputFilename, tmpManifestPath, tmpOutputPath, tmpStat, false, fCallback);
|
|
855
|
+
return fCallback(new Error('No preview tools available.'));
|
|
833
856
|
}
|
|
834
857
|
}
|
|
835
858
|
|
|
@@ -1087,6 +1110,96 @@ class RetoldRemoteImageService extends libFableServiceProviderBase
|
|
|
1087
1110
|
}
|
|
1088
1111
|
}
|
|
1089
1112
|
|
|
1113
|
+
/**
|
|
1114
|
+
* Generate a preview by dispatching to an Ultravisor beacon.
|
|
1115
|
+
* Uses the MediaConversion capability (ImageResize) with a shell
|
|
1116
|
+
* convert fallback. The result is written to the cache directory.
|
|
1117
|
+
*
|
|
1118
|
+
* @param {string} pInputPath - Absolute path to the source image
|
|
1119
|
+
* @param {string} pRelPath - Relative path (for response/logging)
|
|
1120
|
+
* @param {number} pMaxDim - Max dimension
|
|
1121
|
+
* @param {string} pCacheKey - Cache key
|
|
1122
|
+
* @param {string} pOutputFilename - Output filename
|
|
1123
|
+
* @param {string} pCacheDir - Cache directory path
|
|
1124
|
+
* @param {string} pManifestPath - Manifest file path
|
|
1125
|
+
* @param {string} pOutputPath - Output file path
|
|
1126
|
+
* @param {object} pStat - Original file stat
|
|
1127
|
+
* @param {boolean} pIsRaw - Whether this is a raw camera format
|
|
1128
|
+
* @param {Function} fCallback - Callback(pError, pResult)
|
|
1129
|
+
*/
|
|
1130
|
+
_doGeneratePreviewWithDispatcher(pInputPath, pRelPath, pMaxDim, pCacheKey, pOutputFilename, pCacheDir, pManifestPath, pOutputPath, pStat, pIsRaw, fCallback)
|
|
1131
|
+
{
|
|
1132
|
+
let tmpSelf = this;
|
|
1133
|
+
let tmpRelPath;
|
|
1134
|
+
|
|
1135
|
+
try
|
|
1136
|
+
{
|
|
1137
|
+
tmpRelPath = libPath.relative(this.contentPath, pInputPath);
|
|
1138
|
+
}
|
|
1139
|
+
catch (pErr)
|
|
1140
|
+
{
|
|
1141
|
+
return fCallback(new Error('Could not resolve relative path for dispatch.'));
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
if (!tmpRelPath || tmpRelPath.startsWith('..'))
|
|
1145
|
+
{
|
|
1146
|
+
return fCallback(new Error('File is outside content root.'));
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
this._dispatcher.triggerOperation('rr-image-thumbnail',
|
|
1150
|
+
{
|
|
1151
|
+
ImageAddress: '>retold-remote/File/' + tmpRelPath,
|
|
1152
|
+
Width: pMaxDim,
|
|
1153
|
+
Height: pMaxDim,
|
|
1154
|
+
Format: 'jpeg',
|
|
1155
|
+
Quality: this.options.PreviewQuality || 85
|
|
1156
|
+
},
|
|
1157
|
+
(pTriggerError, pResult) =>
|
|
1158
|
+
{
|
|
1159
|
+
if (pTriggerError || !pResult || !pResult.OutputBuffer)
|
|
1160
|
+
{
|
|
1161
|
+
return fCallback(new Error('Operation trigger preview generation failed: ' + (pTriggerError ? pTriggerError.message : 'no output')));
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
try
|
|
1165
|
+
{
|
|
1166
|
+
libFs.writeFileSync(pOutputPath, pResult.OutputBuffer);
|
|
1167
|
+
}
|
|
1168
|
+
catch (pWriteError)
|
|
1169
|
+
{
|
|
1170
|
+
return fCallback(new Error('Failed to write preview output: ' + pWriteError.message));
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
let tmpResult =
|
|
1174
|
+
{
|
|
1175
|
+
Success: true,
|
|
1176
|
+
SourcePath: pRelPath,
|
|
1177
|
+
CacheKey: pCacheKey,
|
|
1178
|
+
OutputFilename: pOutputFilename,
|
|
1179
|
+
Width: pMaxDim,
|
|
1180
|
+
Height: pMaxDim,
|
|
1181
|
+
OrigWidth: 0,
|
|
1182
|
+
OrigHeight: 0,
|
|
1183
|
+
FileSize: pResult.OutputBuffer.length,
|
|
1184
|
+
NeedsPreview: true,
|
|
1185
|
+
IsRawFormat: pIsRaw,
|
|
1186
|
+
GeneratedAt: new Date().toISOString()
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
try
|
|
1190
|
+
{
|
|
1191
|
+
libFs.writeFileSync(pManifestPath, JSON.stringify(tmpResult, null, '\t'));
|
|
1192
|
+
}
|
|
1193
|
+
catch (pWriteError)
|
|
1194
|
+
{
|
|
1195
|
+
tmpSelf.fable.log.warn(`Could not write preview manifest: ${pWriteError.message}`);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
tmpSelf.fable.log.info(`Generated image preview (operation trigger): ${pRelPath}`);
|
|
1199
|
+
return fCallback(null, tmpResult);
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1090
1203
|
// ---------------------------------------------------------------
|
|
1091
1204
|
// DZI tile generation
|
|
1092
1205
|
// ---------------------------------------------------------------
|