Add Show Contact Sheet to server WebUI
This commit is contained in:
parent
4ad22bb46a
commit
41a0553c45
|
@ -80,6 +80,46 @@
|
||||||
|
|
||||||
<div id="alert-container"></div>
|
<div id="alert-container"></div>
|
||||||
|
|
||||||
|
<!-- Contact sheet modal -->
|
||||||
|
<div class="modal fade" id="contactsheetModal" tabindex="-1" aria-labelledby="contactsheetModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" style="max-width: 80vw;">
|
||||||
|
<div class="modal-content modal-content-contactsheet">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h6 class="modal-title title-truncate" id="contactsheetModalLabel">Image Title</h6>
|
||||||
|
<button type="button" class="btn btn-danger fa fa-times-circle" data-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body text-center">
|
||||||
|
<img id="imageModalImage" src="" alt="Contact Sheet" class="img-fluid" style="max-width: 100%; height: auto;" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="deleteRecordingBtn" type="button" class="btn btn-danger"><i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="modal fade" id="contactsheetModal" tabindex="-1" aria-labelledby="contactsheetModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" style="max-width: 80vw;">
|
||||||
|
<div class="modal-content modal-content-contactsheet">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h6 class="modal-title title-truncate" id="contactsheetModalLabel">Image Title</h6>
|
||||||
|
<button type="button" class="btn btn-danger fas fa-times-circle" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span class="sr-only">Close</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body text-center">
|
||||||
|
<img id="imageModalImage" src="placeholder.jpg" alt="Contact Sheet" class="img-fluid" style="max-width: 100%; height: auto;" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-danger fas fa-trash"
|
||||||
|
data-bind="enable: (ko_status() === 'FINISHED' && ko_contactSheet() !== null), click: show, attr: { title: (ko_status() === 'FINISHED' && ko_contactSheet() !== null) ? 'Show contact sheet' : 'No contact sheet' }">
|
||||||
|
<span class="sr-only">Show contact sheet</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
<div class="tab-content" id="myTabContent">
|
<div class="tab-content" id="myTabContent">
|
||||||
<section id="models" class="tab-pane fade active show" role="tabpanel" aria-labelledby="models-tab">
|
<section id="models" class="tab-pane fade active show" role="tabpanel" aria-labelledby="models-tab">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -182,12 +222,12 @@
|
||||||
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'model.name'}">Model</a></th>
|
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'model.name'}">Model</a></th>
|
||||||
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'startDate'}">Date</a></th>
|
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'startDate'}">Date</a></th>
|
||||||
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'ko_status'}">Status</a></th>
|
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'ko_status'}">Status</a></th>
|
||||||
<!-- <th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'progress'}">Progress</a></th> -->
|
|
||||||
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'sizeInByte'}">Size</a></th>
|
<th><a href="#" data-bind="orderable: {collection: 'recordings', field: 'sizeInByte'}">Size</a></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody data-bind="foreach: recordings">
|
<tbody data-bind="foreach: recordings">
|
||||||
|
@ -195,8 +235,10 @@
|
||||||
<td><a data-bind="attr: { href: model.url, title: model.name }, text: model.name"></a></td>
|
<td><a data-bind="attr: { href: model.url, title: model.name }, text: model.name"></a></td>
|
||||||
<td data-bind="text: ko_date"></td>
|
<td data-bind="text: ko_date"></td>
|
||||||
<td data-bind="text: ko_status"></td>
|
<td data-bind="text: ko_status"></td>
|
||||||
<!-- <td data-bind="text: ko_progressString"></td> -->
|
|
||||||
<td data-bind="text: ko_size"></td>
|
<td data-bind="text: ko_size"></td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-secondary fa fa-image" data-bind="enable: (ko_status() == 'FINISHED' && ko_contactSheet() !== null), click: show, attr: { title: (ko_status() == 'FINISHED' && ko_contactSheet() !== null) ? 'Show contact sheet' : 'No contact sheet' }"></button>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-secondary fa fa-play" title="Play recording" data-bind="click: play"></button>
|
<button class="btn btn-secondary fa fa-play" title="Play recording" data-bind="click: play"></button>
|
||||||
</td>
|
</td>
|
||||||
|
@ -207,12 +249,12 @@
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-secondary fa fa-recycle" title="Rerun processing"
|
<button class="btn btn-secondary fa fa-recycle" title="Rerun processing"
|
||||||
data-bind="enable: (ko_status() == 'WAITING' || ko_status() == 'FAILED' || ko_status() == 'FINISHED'),
|
data-bind="enable: (ko_status() == 'WAITING' || ko_status() == 'FAILED' || ko_status() == 'FINISHED'),
|
||||||
click: ctbrec.rerunProcessing"></button>
|
click: rerunProcessing"></button>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-secondary fa fa-trash" title="Delete recording"
|
<button class="btn btn-secondary fa fa-trash" title="Delete recording"
|
||||||
data-bind="enable: (ko_status() == 'FINISHED' || ko_status() == 'WAITING' || ko_status() == 'FAILED'),
|
data-bind="enable: (ko_status() == 'FINISHED' || ko_status() == 'WAITING' || ko_status() == 'FAILED'),
|
||||||
click: ctbrec.deleteRecording"></button>
|
click: deleteRecording"></button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -297,9 +339,10 @@
|
||||||
<script src="vendor/CryptoJS/hmac-sha256.js"></script>
|
<script src="vendor/CryptoJS/hmac-sha256.js"></script>
|
||||||
|
|
||||||
<!-- ctbrec stuff -->
|
<!-- ctbrec stuff -->
|
||||||
|
<script src="config.js"></script>
|
||||||
<script src="recordings.js"></script>
|
<script src="recordings.js"></script>
|
||||||
<script src="models.js"></script>
|
<script src="models.js"></script>
|
||||||
<script src="config.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
let cookieJar = cookie.cookie;
|
let cookieJar = cookie.cookie;
|
||||||
cookieJar.defaults.expires = 365 * 100;
|
cookieJar.defaults.expires = 365 * 100;
|
||||||
|
@ -450,17 +493,17 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addModelKeyPressed(e) {
|
function addModelKeyPressed(e) {
|
||||||
let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
|
let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
|
||||||
|
|
||||||
if (charCode === 13) {
|
if (charCode === 13) {
|
||||||
addModel();
|
addModel();
|
||||||
} else {
|
} else {
|
||||||
$('#addModelByUrl').autocomplete({
|
$('#addModelByUrl').autocomplete({
|
||||||
source: ["AmateurTv:", "BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "CherryTv:", "Dreamcam:", "Fc2Live:", "Flirt4Free:", "LiveJasmin:", "MVLive:", "MyFreeCams:", "SecretFriends:", "Showup:", "Streamate:", "Streamray:", "Stripchat:", "XloveCam:"]
|
source: ["AmateurTv:", "BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "CherryTv:", "Dreamcam:", "Fc2Live:", "Flirt4Free:", "LiveJasmin:", "MVLive:", "MyFreeCams:", "SecretFriends:", "Showup:", "Streamate:", "Streamray:", "Stripchat:", "XloveCam:"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctbrec = {
|
let ctbrec = {
|
||||||
add: function(input, duration, onsuccess) {
|
add: function(input, duration, onsuccess) {
|
||||||
|
@ -588,7 +631,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
rerunProcessing: function(recording) {
|
/* rerunProcessing: function(recording) {
|
||||||
let name = recording.model.name + ' ' + recording.ko_date();
|
let name = recording.model.name + ' ' + recording.ko_date();
|
||||||
try {
|
try {
|
||||||
let action = '{"action": "rerunPostProcessing", "recording": ' + JSON.stringify(recording) + '}';
|
let action = '{"action": "rerunPostProcessing", "recording": ' + JSON.stringify(recording) + '}';
|
||||||
|
@ -634,7 +677,7 @@
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (console) console.log('Unexpected error', e);
|
if (console) console.log('Unexpected error', e);
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
};
|
};
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
@ -682,6 +725,11 @@
|
||||||
throughput
|
throughput
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Required for contact sheets
|
||||||
|
window.onload = function() {
|
||||||
|
loadConfig();
|
||||||
|
};
|
||||||
|
|
||||||
updateOnlineModels();
|
updateOnlineModels();
|
||||||
updateRecordings();
|
updateRecordings();
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,54 @@ function download(recording) {
|
||||||
location.href = src;
|
location.href = src;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rerunProcessing(recording) {
|
||||||
|
let name = recording.model.name + ' ' + recording.ko_date();
|
||||||
|
try {
|
||||||
|
let action = '{"action": "rerunPostProcessing", "recording": ' + JSON.stringify(recording) + '}';
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '../rec',
|
||||||
|
dataType: 'json',
|
||||||
|
async: true,
|
||||||
|
timeout: 60000,
|
||||||
|
headers: {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
||||||
|
data: action
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (console) console.log('Unexpected error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteRecording(recording) {
|
||||||
|
let name = recording.model.name + ' ' + recording.ko_date();
|
||||||
|
try {
|
||||||
|
let action = '{"action": "delete", "recording": ' + JSON.stringify(recording) + '}';
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '../rec',
|
||||||
|
dataType: 'json',
|
||||||
|
async: true,
|
||||||
|
timeout: 60000,
|
||||||
|
headers: {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
|
||||||
|
data: action
|
||||||
|
})
|
||||||
|
.done(function(data) {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
$.notify('Removed recording ' + name, 'info');
|
||||||
|
observableRecordingsArray.remove(recording);
|
||||||
|
} else {
|
||||||
|
$.notify('Removing recording ' + name + ' failed', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail(function(jqXHR, textStatus, errorThrown) {
|
||||||
|
if (console) console.log(textStatus, errorThrown);
|
||||||
|
$.notify('Removing recording ' + name + ' failed', 'error');
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (console) console.log('Unexpected error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function calculateSize(sizeInByte) {
|
function calculateSize(sizeInByte) {
|
||||||
let size = sizeInByte;
|
let size = sizeInByte;
|
||||||
let unit = "Bytes";
|
let unit = "Bytes";
|
||||||
|
@ -84,6 +132,32 @@ function isRecordingInArray(array, recording) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function show(recording) {
|
||||||
|
if (console) console.log("Show: " + recording.ko_contactSheet());
|
||||||
|
let localFilePath = recording.ko_contactSheet();
|
||||||
|
let src = getImageUrl(recording.ko_contactSheet());
|
||||||
|
if (console) console.log("Show: " + src);
|
||||||
|
if (src) {
|
||||||
|
if (console) console.log("Image: " + src);
|
||||||
|
// Update the modal's image source and display the modal
|
||||||
|
let modalImage = document.getElementById('imageModalImage');
|
||||||
|
modalImage.src = src;
|
||||||
|
let fileName = localFilePath.split('\\').pop().split('/').pop();
|
||||||
|
let modalTitle = document.getElementById('contactsheetModalLabel');
|
||||||
|
modalTitle.innerText = fileName; // Set the title to the file name
|
||||||
|
// Show the modal (assuming you are using Bootstrap's modal)
|
||||||
|
$('#contactsheetModal').modal('show');
|
||||||
|
// Update the delete button to use this recording
|
||||||
|
document.getElementById('deleteRecordingBtn').onclick = function() {
|
||||||
|
$('#contactsheetModal').modal('hide');
|
||||||
|
deleteRecording(recording);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.log('No contact sheet available');
|
||||||
|
}
|
||||||
|
document.activeElement.blur();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronizes recordings from the server with the displayed knockout recordings table
|
* Synchronizes recordings from the server with the displayed knockout recordings table
|
||||||
*/
|
*/
|
||||||
|
@ -112,9 +186,11 @@ function syncRecordings(recordings) {
|
||||||
'second' : '2-digit'
|
'second' : '2-digit'
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
recording.ko_progressString = ko.observable(recording.progress === -1 ? '' : recording.progress);
|
|
||||||
recording.ko_size = ko.observable(calculateSize(recording.sizeInByte));
|
recording.ko_size = ko.observable(calculateSize(recording.sizeInByte));
|
||||||
recording.ko_status = ko.observable(recording.status);
|
recording.ko_status = ko.observable(recording.status);
|
||||||
|
recording.ko_contactSheet = ko.observable(
|
||||||
|
recording.associatedFiles?.find(file => file.endsWith('.jpg') || file.endsWith('.png')) || null
|
||||||
|
);
|
||||||
if (recording.singleFile) {
|
if (recording.singleFile) {
|
||||||
recording.playlist = '/hls/' + recording.id;
|
recording.playlist = '/hls/' + recording.id;
|
||||||
} else {
|
} else {
|
||||||
|
@ -134,9 +210,13 @@ function syncRecordings(recordings) {
|
||||||
r.sizeInByte = recording.sizeInByte;
|
r.sizeInByte = recording.sizeInByte;
|
||||||
r.status = recording.status;
|
r.status = recording.status;
|
||||||
r.startDate = recording.startDate;
|
r.startDate = recording.startDate;
|
||||||
r.ko_progressString(recording.progress === -1 ? '' : (recording.progress + '%'));
|
|
||||||
r.ko_size(calculateSize(recording.sizeInByte));
|
r.ko_size(calculateSize(recording.sizeInByte));
|
||||||
r.ko_status(recording.status);
|
r.ko_status(recording.status);
|
||||||
|
r.ko_contactSheet(
|
||||||
|
recording.associatedFiles && recording.associatedFiles.find(file =>
|
||||||
|
file.endsWith('.jpg') || file.endsWith('.png')
|
||||||
|
) || null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,3 +285,26 @@ function updateDiskSpace() {
|
||||||
console.log(textStatus, errorThrown);
|
console.log(textStatus, errorThrown);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getImageUrl(localFilePath) {
|
||||||
|
// if (!localFilePath) return null; // Can be removed since button is disabled if no valid content
|
||||||
|
if (console) console.log("getImageUrl: " + localFilePath);
|
||||||
|
// Find recordingsDir from observableSettingsArray
|
||||||
|
let recordingsDirEntry = ko.utils.arrayFirst(observableSettingsArray(), item => item.key === 'recordingsDir');
|
||||||
|
if (!recordingsDirEntry) return null; // Can be removed since it has to exist in the config
|
||||||
|
let recordingsDir = recordingsDirEntry.ko_value();
|
||||||
|
// Normalize paths to use forward slashes
|
||||||
|
const normalizedLocalPath = localFilePath.replace(/\\/g, '/');
|
||||||
|
const normalizedRecordingsDir = recordingsDir.replace(/\\/g, '/');
|
||||||
|
const basePath = normalizedRecordingsDir.endsWith('/') ?
|
||||||
|
normalizedRecordingsDir :
|
||||||
|
normalizedRecordingsDir + '/';
|
||||||
|
if (console) console.log("normalizedLocalPath: " + normalizedLocalPath);
|
||||||
|
if (console) console.log("normalizedRecordingsDir: " + normalizedRecordingsDir);
|
||||||
|
if (console) console.log("basePath: " + basePath);
|
||||||
|
// Check if localFilePath starts with recordingsDir and replace it
|
||||||
|
if (normalizedLocalPath.startsWith(basePath)) {
|
||||||
|
return "/image/recording/" + normalizedLocalPath.substring(basePath.length);
|
||||||
|
}
|
||||||
|
return null; // Return null if the path doesn't match - in theory shouldn't happen
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue