video_path)) {\n $video_path = $job->video_path;\n } elseif (!empty($job->file_path)) {\n $video_path = $job->file_path;\n } elseif (!empty($job->file_reference)) {\n $video_path = $job->file_reference;\n }\n\nif (!empty($video_path) && strpos($video_path, 'drive.google.com') !== false) {\n\n return self::download_google_drive_file(\n $video_path,\n !empty($job->original_filename) ? $job->original_filename : 'google-drive-video.mp4'\n );\n\n}\n\n if (!empty($job->storage_provider) && $job->storage_provider === 'gcs') {\n $object_key = '';\n\n if (!empty($job->object_key)) {\n $object_key = $job->object_key;\n } elseif (!empty($job->source_reference)) {\n $object_key = $job->source_reference;\n } elseif (!empty($job->file_reference)) {\n $object_key = $job->file_reference;\n } elseif (!empty($job->video_path)) {\n $object_key = $job->video_path;\n }\n\n if (empty($object_key)) {\n return new WP_Error('cmsg_gcs_missing_object', 'Missing GCS object key.');\n }\n\n $uploads = wp_upload_dir();\n $cache_dir = trailingslashit($uploads['basedir']) . 'cmsg-gcs-cache';\n\n if (!is_dir($cache_dir)) {\n wp_mkdir_p($cache_dir);\n }\n\n $local_path = trailingslashit($cache_dir) . basename($object_key);\n\n if (!file_exists($local_path)) {\n $settings = CMSG_Plugin::settings();\n $bucket = $settings['gcs_bucket_name'] ?? '';\n\n if (empty($bucket)) {\n return new WP_Error('cmsg_gcs_missing_bucket', 'Missing GCS bucket name.');\n }\n\n // Force gcloud CLI to use the working credentials copied for www-data.\n putenv('CLOUDSDK_CONFIG=/var/www/.config/gcloud');\n\n $cmd = 'CLOUDSDK_CONFIG=/var/www/.config/gcloud gcloud storage cp ' .\n escapeshellarg('gs://' . $bucket . '/' . ltrim($object_key, '/')) . ' ' .\n escapeshellarg($local_path) . ' 2>&1';\n\n $output = [];\n $code = 0;\n exec($cmd, $output, $code);\n\n if ($code !== 0 || !file_exists($local_path)) {\n return new WP_Error(\n 'cmsg_gcs_download_failed',\n 'Failed to download GCS file: ' . implode("\n", $output)\n );\n }\n\n @chown($local_path, 'www-data');\n @chmod($local_path, 0664);\n }\n\n return $local_path;\n }\n\n return $video_path;\n }\n\nprivate static function download_google_drive_file($drive_url, $original_filename = '') {\n if (empty($drive_url)) {\n return new WP_Error('cmsg_drive_missing_url', 'Missing Google Drive URL.');\n }\n\n $uploads = wp_upload_dir();\n $cache_dir = trailingslashit($uploads['basedir']) . 'cmsg-drive-cache';\n\n if (!is_dir($cache_dir)) {\n wp_mkdir_p($cache_dir);\n }\n\n $safe_name = !empty($original_filename)\n ? sanitize_file_name($original_filename)\n : 'google-drive-video.mp4';\n\n $local_path = trailingslashit($cache_dir) . time() . '-' . $safe_name;\n\n $python = '/opt/subgen-venv/bin/python';\n\n putenv('HOME=/var/www');\n putenv('XDG_CACHE_HOME=/var/www/.cache');\n\n $cmd = escapeshellcmd($python) . ' -m gdown ' .\n escapeshellarg($drive_url) . ' -O ' .\n escapeshellarg($local_path) . ' 2>&1';\n\n $output = [];\n $code = 0;\n exec($cmd, $output, $code);\n\n if ($code !== 0 || !file_exists($local_path)) {\n return new WP_Error(\n 'cmsg_drive_download_failed',\n 'Failed to download Google Drive file: ' . implode("\n", $output)\n );\n }\n\n @chown($local_path, 'www-data');\n @chmod($local_path, 0664);\n\n return $local_path;\n}\n\n private static function send_completion_email($job_id, $srt_path) {\n $job = CMSG_Jobs::get_job($job_id);\n\n if (!$job) {\n error_log('CMSG EMAIL TEST: job not found for email, job_id=' . $job_id);\n return false;\n }\n\n $to = isset($job->requester_email) ? trim($job->requester_email) : '';\n\n if (empty($to) || !is_email($to)) {\n error_log('CMSG EMAIL TEST: invalid or empty recipient email: ' . $to);\n return false;\n }\n\n $subject = 'Your Subtitle File is Ready';\n\n // Temporary direct link. Later this can be upgraded to protected expiring grant links.\n $download_url = site_url('/wp-content/uploads/cmsg-gcs-cache/' . basename($srt_path));\n\n $message = "Hello,\n\n";\n $message .= "Your subtitle file is ready.\n\n";\n $message .= "Click the link below to download your SRT file:\n\n";\n $message .= $download_url . "\n\n";\n $message .= "Thank you,\n";\n $message .= "Crossmarket Films";\n\n $headers = [\n 'Content-Type: text/plain; charset=UTF-8',\n 'From: Crossmarket Films '\n ];\n\n error_log('CMSG EMAIL TEST: sending subtitle email to ' . $to);\n\n $sent = wp_mail($to, $subject, $message, $headers);\n\n if ($sent) {\n error_log('CMSG EMAIL TEST: wp_mail returned true for ' . $to);\n } else {\n error_log('CMSG EMAIL TEST: wp_mail returned false for ' . $to);\n }\n\n return $sent;\n }\n\n\n private static function create_vtt_from_srt($srt_path) {\n if (empty($srt_path) || !file_exists($srt_path)) return '';\n $vtt_path = preg_replace('/\.srt$/i', '.vtt', $srt_path);\n if ($vtt_path === $srt_path) $vtt_path = $srt_path . '.vtt';\n $content = (string) file_get_contents($srt_path);\n $content = preg_replace('/^\xEF\xBB\xBF/', '', $content);\n $content = str_replace(',', '.', $content);\n file_put_contents($vtt_path, "WEBVTT\n\n" . $content);\n return $vtt_path;\n }\n\n public static function process_job($job_id) {\n $job = CMSG_Jobs::get_job($job_id);\n\n if (!$job) {\n return;\n }\n\n if (!CMSG_Jobs::can_job_be_processed($job)) {\n return;\n }\n\n $auth = CMSG_Payments::get_by_id((int) $job->payment_authorization_id);\n\n if (!$auth || !in_array($auth->status, ['used', 'active'], true)) {\n CMSG_Jobs::update_job($job_id, [\n 'status' => 'failed',\n 'log_text' => 'Processing blocked because payment authorization is not valid.'\n ]);\n return;\n }\n\n CMSG_Jobs::update_job($job_id, [\n 'status' => 'processing',\n 'log_text' => 'Starting subtitle generation...'\n ]);\n\n $resolved_video_path = self::resolve_video_path($job);\n\n if (is_wp_error($resolved_video_path)) {\n CMSG_Jobs::update_job($job_id, [\n 'status' => 'failed',\n 'log_text' => $resolved_video_path->get_error_message()\n ]);\n return;\n }\n\n if (empty($resolved_video_path) || !file_exists($resolved_video_path)) {\n CMSG_Jobs::update_job($job_id, [\n 'status' => 'failed',\n 'log_text' => 'Subtitle generation failed. Video file not found: ' . $resolved_video_path\n ]);\n return;\n }\n\n if (!empty($job->storage_provider) && $job->storage_provider === 'gcs') {\n CMSG_Jobs::update_job($job_id, [\n 'log_text' => 'Starting subtitle generation. Downloaded GCS object to local cache: ' . $resolved_video_path\n ]);\n }\n\n $s = CMSG_Plugin::settings();\n\n $python = escapeshellcmd($s['python_binary']);\n $script = escapeshellarg(CMSG_DIR . 'bin/generate_subtitles.py');\n $video = escapeshellarg($resolved_video_path);\n $lang = escapeshellarg(!empty($job->language_code) ? $job->language_code : 'auto');\n $model = escapeshellarg(!empty($job->model_size) ? $job->model_size : $s['default_model']);\n $ffmpeg = escapeshellarg($s['ffmpeg_binary']);\n $mode = escapeshellarg($s['whisper_mode']);\n\n $command = "{$python} {$script} --video {$video} --language {$lang} --model {$model} --ffmpeg {$ffmpeg} --mode {$mode} 2>&1";\n\n $output = [];\n $return_var = 0;\n exec($command, $output, $return_var);\n\n $log = implode("\n", $output);\n\n if ($return_var !== 0) {\n CMSG_Jobs::update_job($job_id, [\n 'status' => 'failed',\n 'log_text' => "Subtitle generation failed.\n" . $log\n ]);\n return;\n }\n\n $srt = '';\n\n foreach ($output as $line) {\n if (strpos($line, 'SRT_PATH=') === 0) {\n $srt = trim(substr($line, 9));\n }\n }\n\n if (empty($srt) || !file_exists($srt)) {\n CMSG_Jobs::update_job($job_id, [\n 'status' => 'failed',\n 'log_text' => 'Subtitle generation failed. No SRT output was created.'\n ]);\n return;\n }\n\n $caption_mode = isset($job->caption_mode) ? (string) $job->caption_mode : 'subtitle';\n $update = [\n 'status' => 'completed',\n 'srt_path' => $srt,\n 'log_text' => 'Subtitle file is ready. A download link has been sent to your email. You may also request a protected download link from the page.'\n ];\n\n if ($caption_mode === 'closed_caption') {\n\n // Generate basic VTT only.\n // YamNet audio-event injection is temporarily disabled because it caused hangs/timeouts.\n $vtt = self::convert_srt_to_vtt($srt);\n\n if (!empty($vtt)) {\n $update['vtt_path'] = $vtt;\n $update['log_text'] = 'Closed Caption Mode completed. Basic VTT was generated, with SRT available for compatibility.';\n }\n }\n }\nprivate static function run_yamnet_detection($video_path) {\n $result = ['cues' => []];\n\n if (empty($video_path) || !file_exists($video_path)) {\n return $result;\n }\n\n $ffmpeg = CMSG_Plugin::settings()['ffmpeg_binary'] ?: '/usr/bin/ffmpeg';\n $audio_path = sys_get_temp_dir() . '/cmsg-yamnet-' . md5($video_path . time()) . '.wav';\n\n $extract_cmd = escapeshellcmd($ffmpeg)\n . ' -y -i ' . escapeshellarg($video_path)\n . ' -ac 1 -ar 16000 '\n . escapeshellarg($audio_path)\n . ' 2>&1';\n\n exec($extract_cmd, $extract_out, $extract_code);\n\n if ($extract_code !== 0 || !file_exists($audio_path)) {\n error_log('CMSG YAMNET AUDIO EXTRACT ERROR: ' . implode("\n", $extract_out));\n return $result;\n }\n\n $script = plugin_dir_path(dirname(__FILE__)) . 'python/yamnet_detect.py';\n\n $cmd = '/opt/subgen-venv/bin/python '\n . escapeshellarg($script) . ' '\n . escapeshellarg($audio_path)\n . ' 2>&1';\n\n exec($cmd, $out, $code);\n\n @unlink($audio_path);\n\n if ($code !== 0 || empty($out)) {\n error_log('CMSG YAMNET ERROR: ' . implode("\n", $out));\n return $result;\n }\n\n $json_text = trim(end($out));\n $decoded = json_decode($json_text, true);\n\n if (is_array($decoded) && isset($decoded['cues'])) {\n return $decoded;\n }\n\n error_log('CMSG YAMNET JSON DECODE ERROR: ' . implode("\n", $out));\n\n return $result;\n}\n\nprivate static function append_yamnet_cues_to_vtt($vtt_path, $cues) {\n if (!file_exists($vtt_path) || empty($cues) || !is_array($cues)) {\n return $vtt_path;\n }\n\n $content = file_get_contents($vtt_path);\n\n foreach ($cues as $cue) {\n if (empty($cue['cue'])) {\n continue;\n }\n\n $start = self::seconds_to_vtt_time((float)($cue['start'] ?? 0));\n $end = self::seconds_to_vtt_time((float)($cue['end'] ?? (($cue['start'] ?? 0) + 1)));\n\n $content .= "\n\n" . $start . ' --> ' . $end . "\n" . sanitize_text_field($cue['cue']);\n }\n\n file_put_contents($vtt_path, $content);\n\n return $vtt_path;\n}\n\nprivate static function seconds_to_vtt_time($seconds) {\n $hours = floor($seconds / 3600);\n $minutes = floor(($seconds % 3600) / 60);\n $secs = floor($seconds % 60);\n $millis = floor(($seconds - floor($seconds)) * 1000);\n\n return sprintf('%02d:%02d:%02d.%03d', $hours, $minutes, $secs, $millis);\n}\n}\n https://crossmarketfilms.com/wp-sitemap-posts-post-1.xmlhttps://crossmarketfilms.com/wp-sitemap-posts-page-1.xmlhttps://crossmarketfilms.com/wp-sitemap-posts-job_listing-1.xmlhttps://crossmarketfilms.com/wp-sitemap-posts-product-1.xmlhttps://crossmarketfilms.com/wp-sitemap-posts-apus_footer-1.xmlhttps://crossmarketfilms.com/wp-sitemap-posts-apus_brand-1.xmlhttps://crossmarketfilms.com/wp-sitemap-posts-apus_testimonial-1.xmlhttps://crossmarketfilms.com/wp-sitemap-taxonomies-category-1.xmlhttps://crossmarketfilms.com/wp-sitemap-taxonomies-product_cat-1.xmlhttps://crossmarketfilms.com/wp-sitemap-users-1.xml