Compare commits

...

5 Commits

Author SHA1 Message Date
Jafea7 32036f9027 Update ConfigServlet.java 2025-05-14 22:24:55 +10:00
Jafea7 66a91a1944 Update CHANGELOG.md 2025-05-14 22:10:29 +10:00
Jafea7 ad78522b95 Update version 2025-05-14 22:10:25 +10:00
Jafea7 d55d2d5b60 Add Record Until to WebUI (@o8o8o8o8o8o8o8o8o8o8o8o8) 2025-05-14 22:07:28 +10:00
Jafea7 3ac3f83ad4 Revert 05f83f7c96 2025-05-14 22:06:15 +10:00
9 changed files with 292 additions and 89 deletions

View File

@ -1,3 +1,109 @@
**DO NOT OVERWRITE YOUR CURRENT INSTALL**, (I will have a good laugh if it deletes your "educational" videos).
I've tested this on Windows 10 and LMDE and it records from each site, haven't tested anything else apart from basic post-processing but, in theory, I haven't messed with anything other than what I've noted below, so groups, etc should still work.
**NOTE:** The included ffmpeg is a static compiled version from the Jellyfin project, if it causes problems for you, replace it with the version out of the original v5.3.0 archives.
**DISCLAIMER:** There most likely are bugs, shit happens.
If this version doesn't do what you want, don't use it ... simple.
Changes from reusedname's v5.3.2 version.
25.05.13
========================
* Added more search options
* Revert 05f83f7c96b280a9b5d74b041421e46ed514364f
* Add Record Until to WebUI
25.05.11
========================
* Fix a bug with Models list display (would sometimes not show anything)
25.05.06
========================
* Move portrait directory from `config/<version>/` to `config` **MOVE YOUR EXISTING DIRECTORY**
**Will no longer be included in the auto-backup of the config**
* Recordings tab is optional
* Replace some deprecated calls
* Replace Logback calls with Slf4j calls (there was a mix)
* Open config directory from Settings->Help
* Version numbering change to vYY.MM.DD
* Update NR Tool URL
25.04.28
========================
* Make side tabs optional for most sites
* Fix WinkTv capture
* Misc deprecated/redundant changes
* Add NR Tool search context menu
* Fix CGF search for CB (new preview URL)
* Change Record Later to Bookmark in various references
25.04.27
========================
* Fix SC login (v3 API from WinkRU)
25.04.03
========================
* Removed:
* News/Logging tabs, see Settings->Help for Logging
* Check for updates
* Non-working sites (ATv, CTv, LJ, MV, & SF)
* Live previews
* Progress column in Recordings tab
* Gaming, Trending, Top-rated side tabs for CB
* Various options in Settings related to above
* Added:
* Button to open the log file under Settings->Help
* Bongacams Mobile tab
* Superchatlive alternative for SC
* Region tab selection for CB, (Restart after selecting)
* Extra Thumbnail sizes
* Extra steps under Split by time/size
* Allow server to access contact sheets
* Pre-input parameters for post-processing remux/transcode
(⚠️ Do not even try it if you have no idea how to use ffmpeg, you can end up with
no recording ... which can happen even if you do know what you're doing)
* Changed:
* Recording tab is now Models (ala WinkRU's version)
* Help and Donate moved to Settings (ala WinkRU's version)
* Config file is saved to a ./config by default (I hate hiding files all over the place)
* Fixed:
* CS audio (reusedname's patch instead of my hack)
* BC room detection (reusedname's improved patch instead of mine)
* F4F thumbnails
5.3.2
========================
* Generalise Flaresolverr for any domain: replace per-site setting (it was just one for CB though) with list of hosts. Previous setting is migrated to new format on first start.
* Improve group handling somewhat. Models in the group are started in priority order when changing priority, pausing/resuming, and after recording restart has failed. The solution is kind of brute force right now and may cause excessive checks in some cases. I also noticed that sometimes the highest priority model is not on the recording.
* Fix Bongacams online check: remove basic one, do the complete check straight away. Also update its logic to check new field.
* Reduce config save spam from recorder operations (400ms delay default, configurable). Helps with Suspend/Resume all actions.
* Add a couple of server relevant settings to the WebUI Settings page by @Jafea7
* Better spec compliance regarding playlist update timing. The delay was hardcoded to 1 second, now it is taken from the playlist (usually 2 seconds). This also Should reduce network pressure and timeouts.
* New settings for max concurrent http requests (total and per host). Increasing them should reduce timeouts with a lot of active recordings. Found in Advanced group.
* Fix zombie recordings in some cases.
* Fix group precondition not respecting priority.
* Add online state column (hidden by default). Fix capitalization.
* Fix Stripchat login
* Set model state offline if online check is skipped.
* Optimize model updates in the WebUI.
5.3.1
========================
* Flaresolverr support for CB.
* Performance fixes/improvements (handle more concurrent recordings).
* Camsoda sound fix (@Jafea7 I ended up editing the site model class, not the AbstractHlsDownload).
* Missing Stripchat thumbnails fix.
* Client: Show grouped model count in status bar.
* Web interface performance fix by crazy hacker man.
* Some hlsdl stability improvements.
* Bandwidth meter support for hlsdl downloads.
5.3.0
========================
* Added menu entry to force recording of models without changing the prio

View File

@ -8,7 +8,7 @@
<parent>
<groupId>ctbrec</groupId>
<artifactId>master</artifactId>
<version>25.05.11</version>
<version>25.05.13</version>
<relativePath>../master</relativePath>
</parent>

View File

@ -8,7 +8,7 @@
<parent>
<groupId>ctbrec</groupId>
<artifactId>master</artifactId>
<version>25.05.11</version>
<version>25.05.13</version>
<relativePath>../master</relativePath>
</parent>

View File

@ -11,7 +11,7 @@
<groupId>ctbrec</groupId>
<artifactId>master</artifactId>
<packaging>pom</packaging>
<version>25.05.11</version>
<version>25.05.13</version>
<modules>
<module>../common</module>

View File

@ -8,7 +8,7 @@
<parent>
<groupId>ctbrec</groupId>
<artifactId>master</artifactId>
<version>25.05.11</version>
<version>25.05.13</version>
<relativePath>../master</relativePath>
</parent>

View File

@ -53,7 +53,7 @@ public class ConfigServlet extends AbstractCtbrecServlet {
JSONArray json = new JSONArray();
addParameter("concurrentRecordings", "Concurrent Recordings", DataType.INTEGER, settings.concurrentRecordings, json);
addParameter("chaturbateMsBetweenRequests", "Chaturbate time between requests (ms)", DataType.INTEGER, settings.chaturbateMsBetweenRequests, json);
addParameter("defaultPriority", "Default Priority", DataType.INTEGER, settings.defaultPriority, json);
// addParameter("defaultPriority", "Default Priority", DataType.INTEGER, settings.defaultPriority, json);
addParameter("deleteOrphanedRecordingMetadata", "Delete orphaned recording metadata", DataType.BOOLEAN, settings.deleteOrphanedRecordingMetadata, json);
addParameter("ffmpegFileSuffix", "File Suffix", DataType.STRING, settings.ffmpegFileSuffix, json);
addParameter("ffmpegMergedDownloadArgs", "FFmpeg Parameters", DataType.STRING, settings.ffmpegMergedDownloadArgs, json);

View File

@ -303,37 +303,6 @@ public class RecorderServlet extends AbstractCtbrecServlet {
for (Site site : sites) {
Model model = site.createModelFromUrl(url);
if (model != null) {
// Copy over all properties from the request model
ModelDto modelDto = request.getModel();
Integer priority = modelDto.getPriority();
if (priority != null) {
model.setPriority(priority);
}
// Boolean properties - use auto-boxing
Boolean forcePriority = modelDto.isForcePriority();
if (forcePriority != null) {
model.setForcePriority(forcePriority);
}
Boolean suspended = modelDto.isSuspended();
if (suspended != null) {
model.setSuspended(suspended);
}
Boolean bookmarked = modelDto.isBookmarked();
if (bookmarked != null) {
model.setMarkedForLaterRecording(bookmarked);
}
if (modelDto.getRecordUntil() != null) {
model.setRecordUntil(modelDto.getRecordUntil());
}
if (modelDto.getRecordUntilSubsequentAction() != null) {
model.setRecordUntilSubsequentAction(modelDto.getRecordUntilSubsequentAction());
}
recorder.addModel(model);
return;
}
@ -350,39 +319,8 @@ public class RecorderServlet extends AbstractCtbrecServlet {
String modelName = input[1];
for (Site site : sites) {
if (Objects.equals(siteName.toLowerCase(), site.getClass().getSimpleName().toLowerCase())) {
Model model = site.createModel(modelName);
// Copy over all properties from the request model
ModelDto modelDto = request.getModel();
Integer priority = modelDto.getPriority();
if (priority != null) {
model.setPriority(priority);
}
// Boolean properties - use auto-boxing
Boolean forcePriority = modelDto.isForcePriority();
if (forcePriority != null) {
model.setForcePriority(forcePriority);
}
Boolean suspended = modelDto.isSuspended();
if (suspended != null) {
model.setSuspended(suspended);
}
Boolean bookmarked = modelDto.isBookmarked();
if (bookmarked != null) {
model.setMarkedForLaterRecording(bookmarked);
}
if (modelDto.getRecordUntil() != null) {
model.setRecordUntil(modelDto.getRecordUntil());
}
if (modelDto.getRecordUntilSubsequentAction() != null) {
model.setRecordUntilSubsequentAction(modelDto.getRecordUntilSubsequentAction());
}
recorder.addModel(model);
Model m = site.createModel(modelName);
recorder.addModel(m);
return;
}
}

View File

@ -7,9 +7,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=yes">
<meta name="description" content="CTB Recorder is a free recording software for Chaturbate">
<meta name="author" content="">
<meta name="version" content="${project.version}">
<meta name="version" content="5.3.2">
<title>CTB Recorder ${project.version}</title>
<title>CTB Recorder 5.3.2</title>
<!-- Bootstrap core CSS -->
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
@ -87,8 +87,37 @@
<div class="col-lg-10 mx-auto">
<div class="form-group">
<label for="addModelByUrl">Add Model</label>
<input type="text" class="form-control" id="addModelByUrl"
placeholder="e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/" onKeyUp="addModelKeyPressed(event)">
<div class="input-group">
<input type="text" class="form-control" id="addModelByUrl"
placeholder="e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/" onKeyUp="addModelKeyPressed(event)">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="toggleStopAtOptions">
<i class="fa fa-clock"></i>
</button>
<button class="btn btn-primary" type="button" onclick="addModel()">
Add
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Stop At Options - Initially hidden -->
<div class="row" id="stopAtOptionsRow" style="display: none;">
<div class="col-lg-10 mx-auto">
<div class="form-group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Stop at</span>
</div>
<input type="datetime-local" class="form-control" id="recordUntilDate">
<div class="input-group-append">
<select class="form-control" id="recordUntilAction">
<option value="REMOVE">REMOVE</option>
<option value="PAUSE">PAUSE</option>
</select>
</div>
</div>
</div>
</div>
</div>
@ -111,7 +140,7 @@
<td><a data-bind="attr: { href: ko_url, title: ko_name }, text: ko_name"></a></td>
<td><span data-bind="checked: ko_online, class: ko_online() ? `fa fa-check-square checkmark-green` : ``"
style="font-size: 2em"></span></td>
<td><span data-bind="checked: ko_recording, class: ko_recording() ? `fa fa-circle red` : ``" style="font-size: 2em"></span></td>
<td><span data-bind="checked: ko_recording, class: ko_recording() ? `fa fa-circle red` : ``" style="font-size: 2em; width: 40px; display: inline-block; text-align: center;"></span><span data-bind="if: ko_recordUntil && ko_recordUntil() !== undefined && ko_recordUntil() !== null && ko_recordUntil() !== 9000000000000000000"><i class="fa fa-clock" title="Scheduled stop time" style="font-size: 2em; color: #ffffff;"></i></span></td>
<td>
<button class="btn btn-secondary fa fa-play" title="Resume recording"
data-bind="click: ctbrec.resume, visible: ko_suspended"></button>
@ -293,24 +322,146 @@
};
let hmac;
function addModelKeyPressed(e) {
let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
// Toggle visibility of stopAt options
$(document).ready(function() {
$('#toggleStopAtOptions').click(function() {
$('#stopAtOptionsRow').toggle();
// Set default datetime to current time + 1 hour when showing options
if ($('#stopAtOptionsRow').is(':visible') && !$('#recordUntilDate').val()) {
let now = new Date();
now.setHours(now.getHours() + 1);
// Format the date for datetime-local input
let year = now.getFullYear();
let month = (now.getMonth() + 1).toString().padStart(2, '0');
let day = now.getDate().toString().padStart(2, '0');
let hours = now.getHours().toString().padStart(2, '0');
let minutes = now.getMinutes().toString().padStart(2, '0');
$('#recordUntilDate').val(`${year}-${month}-${day}T${hours}:${minutes}`);
}
});
});
// Function to add model (shared by Enter key and Add button)
function addModel() {
let val = $('#addModelByUrl').val();
let duration = $('#recordingDuration').val(); // Get the recording duration input
if (charCode === 13) {
// Check if a valid duration is provided
let durationMinutes = duration ? parseInt(duration) : undefined;
ctbrec.add(val, durationMinutes, function() {
$('#addModelByUrl').val('');
$('#recordingDuration').val(''); // Clear the recording duration input
});
} else {
$('#addModelByUrl').autocomplete({
source: ["BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "Dreamcam:", "Fc2Live:", "Flirt4Free:", "MyFreeCams:", "Showup:", "Streamate:", "Streamray:", "Stripchat:", "XloveCam:"]
});
}
if (!val) return; // Don't do anything if the input is empty
// Store if we need to add stopAt parameters
let useStopAt = $('#stopAtOptionsRow').is(':visible') && $('#recordUntilDate').val();
let stopAtDate = useStopAt ? $('#recordUntilDate').val() : null;
let stopAtAction = useStopAt ? $('#recordUntilAction').val() : null;
let modelInput = val;
// Step 1: Add the model using the standard approach
ctbrec.add(val, undefined, function() {
console.log("Model added successfully");
$('#addModelByUrl').val('');
if (useStopAt) {
// Step 2: After a delay, set stopAt parameters
setTimeout(function() {
// Extract model name from the input
let modelName = modelInput;
if (modelName.includes(':')) {
modelName = modelName.split(':')[1];
} else if (modelName.includes('/')) {
modelName = modelName.split('/').filter(Boolean).pop();
}
console.log("Looking for model: " + modelName);
console.log("Models in array:", observableModelsArray());
// Try to find the model in the observableModelsArray
let existingModel = null;
// Try various ways to find the model
for (let i = 0; i < observableModelsArray().length; i++) {
let model = observableModelsArray()[i];
if (model.name === modelName) {
existingModel = model;
break;
}
if (model.ko_name && typeof model.ko_name === 'function' && model.ko_name() === modelName) {
existingModel = model;
break;
}
// Check if URL contains the model name
if (model.url && model.url.includes(modelName)) {
existingModel = model;
break;
}
}
if (!existingModel) {
console.log("Could not find the model. Creating a simple model object instead.");
// If we can't find the model, create a simple object with just the necessary properties
existingModel = { name: modelName };
}
// Add stopAt parameters to the model
// Always use milliseconds for recordUntil
let dateTimestamp = new Date(stopAtDate).getTime();
existingModel.recordUntil = dateTimestamp;
existingModel.recordUntilSubsequentAction = stopAtAction;
console.log("Using milliseconds for recordUntil. Value: " + existingModel.recordUntil);
console.log("Model with stopAt parameters:", existingModel);
// Send the stopAt action
let stopAtMsg = '{"action": "stopAt", "model": ' + JSON.stringify(existingModel) + '}';
$.ajax({
type: 'POST',
url: '../rec',
dataType: 'json',
async: true,
timeout: 60000,
headers: {'CTBREC-HMAC': CryptoJS.HmacSHA256(stopAtMsg, hmac)},
data: stopAtMsg
})
.done(function(data) {
console.log("StopAt response:", data);
if (data.status === 'success') {
// Use simple date formatting
let date = new Date(stopAtDate).toLocaleString();
$.notify('Recording will stop at ' + date, 'info');
// Reset the datetime input and hide the options
$('#recordUntilDate').val('');
$('#stopAtOptionsRow').hide();
} else {
$.notify('Setting stopAt parameters failed', 'error');
}
})
.fail(function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
$.notify('Setting stopAt parameters failed', 'error');
});
}, 3000); // Wait 3 seconds to make sure the model is registered
}
});
}
function addModelKeyPressed(e) {
let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
if (charCode === 13) {
addModel();
} else {
$('#addModelByUrl').autocomplete({
source: ["AmateurTv:", "BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "CherryTv:", "Dreamcam:", "Fc2Live:", "Flirt4Free:", "LiveJasmin:", "MVLive:", "MyFreeCams:", "SecretFriends:", "Showup:", "Streamate:", "Streamray:", "Stripchat:", "XloveCam:"]
});
}
}
let ctbrec = {
add: function(input, duration, onsuccess) {
try {

View File

@ -68,6 +68,7 @@ function syncModels(models) {
model.ko_recording = ko.observable(model.online && !model.suspended);
//model.ko_recording_class = ko.observable( (model.online && !model.suspended) ? 'fa fa-circle red' : '' );
model.ko_suspended = ko.observable(model.suspended);
model.ko_recordUntil = ko.observable(model.recordUntil);
model.swallowEvents = false;
model.ko_suspended.subscribe(function(checked) {
if (model.swallowEvents) {
@ -98,6 +99,13 @@ function syncModels(models) {
m.ko_suspended(model.suspended);
m.swallowEvents = false;
m.ko_recording(m.ko_online() && !m.ko_suspended());
// Update the recordUntil property to ensure clock icon refreshes
if (m.ko_recordUntil && typeof m.ko_recordUntil === 'function') {
m.ko_recordUntil(model.recordUntil);
} else {
m.ko_recordUntil = ko.observable(model.recordUntil);
}
}
}
}