Compare commits
5 Commits
761b472c5e
...
32036f9027
Author | SHA1 | Date |
---|---|---|
|
32036f9027 | |
|
66a91a1944 | |
|
ad78522b95 | |
|
d55d2d5b60 | |
|
3ac3f83ad4 |
106
CHANGELOG.md
106
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue