Compare commits
No commits in common. "e1a54259df029ca2f43de74f7293e1da15662f7f" and "981c56ed24f92f5a2f60d713507c7c4bcb440246" have entirely different histories.
e1a54259df
...
981c56ed24
4 changed files with 25 additions and 92 deletions
|
|
@ -2,8 +2,6 @@
|
||||||
<script>document.querySelectorAll('textarea[data-counter]').forEach(function(t){var c=document.getElementById(t.dataset.counter);function u(){c.textContent=t.value.length+' / '+t.maxLength+' characters';}t.addEventListener('input',u);u();});</script>
|
<script>document.querySelectorAll('textarea[data-counter]').forEach(function(t){var c=document.getElementById(t.dataset.counter);function u(){c.textContent=t.value.length+' / '+t.maxLength+' characters';}t.addEventListener('input',u);u();});</script>
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(function(){
|
||||||
const DESCRIBE_MAX_FRAMES = 32;
|
|
||||||
|
|
||||||
function attachDescribeButtons(){
|
function attachDescribeButtons(){
|
||||||
document.querySelectorAll('.admin-video-row').forEach(function(row){
|
document.querySelectorAll('.admin-video-row').forEach(function(row){
|
||||||
if (row.querySelector('.describe-button')) return;
|
if (row.querySelector('.describe-button')) return;
|
||||||
|
|
@ -161,7 +159,7 @@
|
||||||
|
|
||||||
function runDescribeRequest(job, articleId, additionalPrompt, selectedPersonality){
|
function runDescribeRequest(job, articleId, additionalPrompt, selectedPersonality){
|
||||||
showDescribeBusy();
|
showDescribeBusy();
|
||||||
var body = 'id=' + encodeURIComponent(articleId) + '&job=' + encodeURIComponent(job) + '&max_frames=' + encodeURIComponent(DESCRIBE_MAX_FRAMES);
|
var body = 'id=' + encodeURIComponent(articleId) + '&job=' + encodeURIComponent(job) + '&max_frames=16';
|
||||||
body += '&personality=' + encodeURIComponent(selectedPersonality || 'auto');
|
body += '&personality=' + encodeURIComponent(selectedPersonality || 'auto');
|
||||||
if (additionalPrompt && additionalPrompt.trim()) {
|
if (additionalPrompt && additionalPrompt.trim()) {
|
||||||
body += '&additional_prompt=' + encodeURIComponent(additionalPrompt);
|
body += '&additional_prompt=' + encodeURIComponent(additionalPrompt);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ STATE_FILE="${SCRIPT_DIR}/.personality_index"
|
||||||
IN_DIR="${1:?Usage: $0 <input-dir> [\"Title\"] [\"Location\"] [max_frames] [additional_prompt]}"
|
IN_DIR="${1:?Usage: $0 <input-dir> [\"Title\"] [\"Location\"] [max_frames] [additional_prompt]}"
|
||||||
TITLE="${2:-}"
|
TITLE="${2:-}"
|
||||||
LOCATION="${3:-}"
|
LOCATION="${3:-}"
|
||||||
MAX_FRAMES="${4:-16}"
|
MAX_FRAMES="${4:-12}"
|
||||||
ADDITIONAL_PROMPT="${5:-}"
|
ADDITIONAL_PROMPT="${5:-}"
|
||||||
SELECTED_PERSONALITY="${6:-}"
|
SELECTED_PERSONALITY="${6:-}"
|
||||||
if [ "$ADDITIONAL_PROMPT" = "no-append" ]; then
|
if [ "$ADDITIONAL_PROMPT" = "no-append" ]; then
|
||||||
|
|
@ -202,6 +202,14 @@ PROMPT=$(cat <<EOF
|
||||||
Look at these captured frames from a motorcycle trip.
|
Look at these captured frames from a motorcycle trip.
|
||||||
You are writing as the main character of a motorcycle/travel blog.
|
You are writing as the main character of a motorcycle/travel blog.
|
||||||
|
|
||||||
|
Style intensity:
|
||||||
|
Use the selected personality at maximum intensity.
|
||||||
|
Do not be subtle.
|
||||||
|
Do not merely sprinkle the style on top.
|
||||||
|
Every sentence should feel shaped by that personality.
|
||||||
|
The voice should be provocative, opinionated, funny, and slightly over the top.
|
||||||
|
Roast the situation, the road, the weather, the narrator, or visible absurdities.
|
||||||
|
Avoid generic travel-blog language.
|
||||||
Use the following personality style, but never mention the character name:
|
Use the following personality style, but never mention the character name:
|
||||||
"$PERSONALITY"
|
"$PERSONALITY"
|
||||||
|
|
||||||
|
|
@ -213,22 +221,25 @@ ${ADDITIONAL_PROMPT_BLOCK}
|
||||||
Create valid JSON only.
|
Create valid JSON only.
|
||||||
|
|
||||||
Required fields:
|
Required fields:
|
||||||
- quote_da: Write exactly one sentence in Danish in the style of Anders Matthesen. The sentence should sound like a sharp, self-deprecating observation from a middle-aged motorcycle traveler who often ends up in absurd or unexpected situations.
|
- personality: exactly "$PERSONALITY_KEY"
|
||||||
- personality: exactly "$PERSONALITY_KEY", used for teaser and description generation.
|
- quote_da: exactly one natural Danish sentence
|
||||||
- teaser: English, max 32 words
|
- teaser: English, max 32 words
|
||||||
- description: English, up to 448 words
|
- description: English, up to 448 words
|
||||||
- tags: 5 to 8 lowercase short tags
|
- tags: 5 to 8 lowercase short tags
|
||||||
|
|
||||||
Content rules:
|
Content rules:
|
||||||
- Use the selected personality at maximum intensity.
|
- Do not simply summarize the trip.
|
||||||
- Do not be subtle.
|
- Try to be positive, unless the personality is inherently negative, in which case embrace that negativity fully.
|
||||||
- The voice MUST BE provocative, opinionated, funny, and slightly over the top.
|
|
||||||
- Highlight the funniest, strangest, most beautiful, or most ridiculous visible aspect.
|
- Highlight the funniest, strangest, most beautiful, or most ridiculous visible aspect.
|
||||||
|
- The description must have a beginning, middle, and end.
|
||||||
|
- Mention the overall experience, mood, road, weather, landscape, and travel feeling if visible.
|
||||||
|
- Include at least one memorable or amusing observation.
|
||||||
|
- End with a concluding thought.
|
||||||
- Be challenging, provocative, witty, and playful.
|
- Be challenging, provocative, witty, and playful.
|
||||||
- Roasting is allowed.
|
- Roasting is allowed.
|
||||||
- Profanity is allowed only if it fits the selected personality.
|
- Profanity is allowed only if it fits the selected personality.
|
||||||
- Do not invent specific facts that are not visible or provided.
|
- Do not invent specific facts that are not visible or provided.
|
||||||
- Description or teaser MUST contain at least one of the following words (preferably more): subscribe, contact, journal. Don't use them in teaser, only in description.
|
- Description or teaser MUST contain at least one of the following words (preferably more): subscribe, contact, journal. It's ok to use the same word mutliple times. Don't use them in teaser, only in description.
|
||||||
|
|
||||||
JSON rules:
|
JSON rules:
|
||||||
- Output JSON only.
|
- Output JSON only.
|
||||||
|
|
|
||||||
|
|
@ -4,73 +4,8 @@ mvlog_require_login();
|
||||||
$config = require __DIR__ . '/config.php';
|
$config = require __DIR__ . '/config.php';
|
||||||
header('Content-Type: application/json; charset=UTF-8');
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||||
|
|
||||||
function mvlog_job_status_log(string $message): void {
|
|
||||||
$log = __DIR__ . '/logs/job_status_cleanup.log';
|
|
||||||
$line = '[' . date('c') . '] ' . $message . "\n";
|
|
||||||
@file_put_contents($log, $line, FILE_APPEND | LOCK_EX);
|
|
||||||
@chmod($log, 0664);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mvlog_worker_running(): bool {
|
|
||||||
$out = [];
|
|
||||||
$rc = 1;
|
|
||||||
@exec('systemctl is-active --quiet mvlog-worker.service 2>/dev/null', $out, $rc);
|
|
||||||
if ($rc === 0) return true;
|
|
||||||
|
|
||||||
$ps = @shell_exec('ps -eo args= 2>/dev/null');
|
|
||||||
if (!is_string($ps) || $ps === '') return false;
|
|
||||||
foreach (explode("\n", $ps) as $line) {
|
|
||||||
if (strpos($line, 'mvlog_worker.py') !== false && strpos($line, '--remote-root /var/www/html') !== false) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mvlog_rm_rf(string $path): void {
|
|
||||||
if (!is_dir($path)) return;
|
|
||||||
$items = scandir($path);
|
|
||||||
if (!is_array($items)) return;
|
|
||||||
foreach ($items as $item) {
|
|
||||||
if ($item === '.' || $item === '..') continue;
|
|
||||||
$child = $path . '/' . $item;
|
|
||||||
if (is_dir($child) && !is_link($child)) mvlog_rm_rf($child);
|
|
||||||
else @unlink($child);
|
|
||||||
}
|
|
||||||
@rmdir($path);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mvlog_cleanup_stale_processing(string $dir, string $name, array $state, string $lockDir, bool $locked, string $status, string $articleId): array {
|
|
||||||
$stateFile = $dir . '/.movmaker-state.json';
|
|
||||||
$started = '';
|
|
||||||
if (is_file($lockDir . '/started_at')) $started = trim((string)@file_get_contents($lockDir . '/started_at'));
|
|
||||||
if ($started === '') $started = (string)($state['started_at'] ?? $state['updated_at'] ?? '');
|
|
||||||
|
|
||||||
$backup = '';
|
|
||||||
if (is_file($stateFile)) {
|
|
||||||
$backup = $stateFile . '.bak.' . date('YmdHis');
|
|
||||||
@copy($stateFile, $backup);
|
|
||||||
@chmod($backup, 0664);
|
|
||||||
}
|
|
||||||
|
|
||||||
$state['status'] = 'error';
|
|
||||||
$state['updated_at'] = gmdate('c');
|
|
||||||
if ($articleId !== '') $state['article_id'] = $articleId;
|
|
||||||
$state['message'] = 'Previous render appears abandoned: no mvlog-worker process is running; stale lock/processing state was cleaned by job_status.php. Re-enable Render to retry.';
|
|
||||||
$json = json_encode($state, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
|
|
||||||
@file_put_contents($stateFile, $json, LOCK_EX);
|
|
||||||
@chmod($stateFile, 0664);
|
|
||||||
if ($locked) mvlog_rm_rf($lockDir);
|
|
||||||
|
|
||||||
mvlog_job_status_log('cleaned stale processing job=' . $name . ' article_id=' . ($articleId ?: '-') . ' started_at=' . ($started ?: '-') . ' status=' . ($status ?: '-') . ' lock=' . ($locked ? 'yes' : 'no') . ' backup=' . ($backup ?: '-'));
|
|
||||||
return $state;
|
|
||||||
}
|
|
||||||
|
|
||||||
$jobs = [];
|
$jobs = [];
|
||||||
$switches = [];
|
$switches = [];
|
||||||
$cleaned = [];
|
|
||||||
$workerRunning = mvlog_worker_running();
|
|
||||||
$dirs = glob($config['uploads_dir'] . '/*', GLOB_ONLYDIR) ?: [];
|
$dirs = glob($config['uploads_dir'] . '/*', GLOB_ONLYDIR) ?: [];
|
||||||
foreach ($dirs as $dir) {
|
foreach ($dirs as $dir) {
|
||||||
$name = basename($dir);
|
$name = basename($dir);
|
||||||
|
|
@ -78,31 +13,21 @@ foreach ($dirs as $dir) {
|
||||||
$lockDir = $dir . '/.movmaker-lock';
|
$lockDir = $dir . '/.movmaker-lock';
|
||||||
$idFile = $dir . '/.mvlog-id';
|
$idFile = $dir . '/.mvlog-id';
|
||||||
$state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
|
$state = is_file($stateFile) ? json_decode((string)file_get_contents($stateFile), true) : [];
|
||||||
if (!is_array($state)) $state = [];
|
$status = is_array($state) ? (string)($state['status'] ?? '') : '';
|
||||||
$status = (string)($state['status'] ?? '');
|
$articleId = is_array($state) ? strtolower((string)($state['article_id'] ?? '')) : '';
|
||||||
$articleId = strtolower((string)($state['article_id'] ?? ''));
|
|
||||||
if ($articleId === '' && is_file($idFile)) $articleId = strtolower(trim((string)file_get_contents($idFile)));
|
if ($articleId === '' && is_file($idFile)) $articleId = strtolower(trim((string)file_get_contents($idFile)));
|
||||||
if (!preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $articleId)) $articleId = '';
|
if (!preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $articleId)) $articleId = '';
|
||||||
$locked = is_dir($lockDir);
|
|
||||||
|
|
||||||
if (!$workerRunning && ($locked || $status === 'processing')) {
|
|
||||||
$state = mvlog_cleanup_stale_processing($dir, $name, $state, $lockDir, $locked, $status, $articleId);
|
|
||||||
$status = (string)($state['status'] ?? '');
|
|
||||||
$locked = is_dir($lockDir);
|
|
||||||
$cleaned[] = ['dir' => $name, 'id' => $articleId, 'status' => $status];
|
|
||||||
}
|
|
||||||
|
|
||||||
$switches[] = [
|
$switches[] = [
|
||||||
'dir' => $name,
|
'dir' => $name,
|
||||||
'id' => $articleId,
|
'id' => $articleId,
|
||||||
'enabled' => is_file($dir . '/.movmaker-enabled'),
|
'enabled' => is_file($dir . '/.movmaker-enabled'),
|
||||||
'preview' => is_file($dir . '/.movmaker-preview'),
|
'preview' => is_file($dir . '/.movmaker-preview'),
|
||||||
];
|
];
|
||||||
|
$locked = is_dir($lockDir);
|
||||||
if ($locked || $status === 'processing') {
|
if ($locked || $status === 'processing') {
|
||||||
$started = '';
|
$started = '';
|
||||||
if (is_file($lockDir . '/started_at')) $started = trim((string)file_get_contents($lockDir . '/started_at'));
|
if (is_file($lockDir . '/started_at')) $started = trim((string)file_get_contents($lockDir . '/started_at'));
|
||||||
if ($started === '') $started = (string)($state['started_at'] ?? $state['updated_at'] ?? '');
|
if ($started === '' && is_array($state)) $started = (string)($state['started_at'] ?? $state['updated_at'] ?? '');
|
||||||
$jobs[] = [
|
$jobs[] = [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'id' => $articleId,
|
'id' => $articleId,
|
||||||
|
|
@ -111,4 +36,4 @@ foreach ($dirs as $dir) {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
echo json_encode(['jobs' => $jobs, 'switches' => $switches, 'cleaned' => $cleaned, 'worker_running' => $workerRunning, 'checked_at' => date('c')], JSON_UNESCAPED_UNICODE);
|
echo json_encode(['jobs' => $jobs, 'switches' => $switches, 'checked_at' => date('c')], JSON_UNESCAPED_UNICODE);
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,11 @@ require __DIR__ . '/../auth.php';
|
||||||
mvlog_require_login();
|
mvlog_require_login();
|
||||||
|
|
||||||
$MVLOG_ROOT = dirname(__DIR__);
|
$MVLOG_ROOT = dirname(__DIR__);
|
||||||
const DESCRIBE_DEFAULT_MAX_FRAMES = 32;
|
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
$id = strtolower(trim((string)($_POST['id'] ?? '')));
|
$id = strtolower(trim((string)($_POST['id'] ?? '')));
|
||||||
$job = trim((string)($_POST['job'] ?? ''));
|
$job = trim((string)($_POST['job'] ?? ''));
|
||||||
$max_frames = isset($_POST['max_frames']) ? intval($_POST['max_frames']) : DESCRIBE_DEFAULT_MAX_FRAMES;
|
$max_frames = isset($_POST['max_frames']) ? intval($_POST['max_frames']) : 16;
|
||||||
$additional_prompt = trim((string)($_POST['additional_prompt'] ?? ''));
|
$additional_prompt = trim((string)($_POST['additional_prompt'] ?? ''));
|
||||||
$personality = strtolower(trim((string)($_POST['personality'] ?? 'auto')));
|
$personality = strtolower(trim((string)($_POST['personality'] ?? 'auto')));
|
||||||
if (!preg_match('/^[a-z0-9_-]+$/', $personality)) $personality = 'auto';
|
if (!preg_match('/^[a-z0-9_-]+$/', $personality)) $personality = 'auto';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue