Fix: Automatic multi-directory video path resolution for torrent in-browser preview streaming
This commit is contained in:
@@ -4002,7 +4002,7 @@ Return ONLY a valid JSON object with an array named "results":
|
|||||||
renderCurrentVideoPreview();
|
renderCurrentVideoPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
function openVideoPreviewForTorrent(hash) {
|
async function openVideoPreviewForTorrent(hash) {
|
||||||
const query = searchInput.value.trim().toLowerCase();
|
const query = searchInput.value.trim().toLowerCase();
|
||||||
const filtered = qbState.torrents.filter(t => {
|
const filtered = qbState.torrents.filter(t => {
|
||||||
if (!query) return true;
|
if (!query) return true;
|
||||||
@@ -4012,6 +4012,11 @@ Return ONLY a valid JSON object with an array named "results":
|
|||||||
const idx = filtered.findIndex(t => t.hash === hash);
|
const idx = filtered.findIndex(t => t.hash === hash);
|
||||||
if (idx === -1) return;
|
if (idx === -1) return;
|
||||||
|
|
||||||
|
const item = filtered[idx];
|
||||||
|
if (!item.innerFiles || item.innerFiles.length === 0) {
|
||||||
|
item.innerFiles = await fetchTorrentFiles(item.hash);
|
||||||
|
}
|
||||||
|
|
||||||
previewState.activeType = 'qb';
|
previewState.activeType = 'qb';
|
||||||
previewState.currentList = filtered;
|
previewState.currentList = filtered;
|
||||||
previewState.currentIndex = idx;
|
previewState.currentIndex = idx;
|
||||||
@@ -4034,8 +4039,17 @@ Return ONLY a valid JSON object with an array named "results":
|
|||||||
streamPath = item.path;
|
streamPath = item.path;
|
||||||
} else {
|
} else {
|
||||||
// qb torrent
|
// qb torrent
|
||||||
const base = (item.save_path || '/downloads').replace(/\/+$/, '');
|
if (item.innerFiles && item.innerFiles.length > 0) {
|
||||||
streamPath = `${base}/${item.name}`;
|
const videoFile = item.innerFiles.find(f => /\.(mp4|mkv|avi|mov|webm|m4v|ts)$/i.test(f.name));
|
||||||
|
if (videoFile) {
|
||||||
|
const base = (item.save_path || '/downloads').replace(/\/+$/, '');
|
||||||
|
streamPath = `${base}/${videoFile.name}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!streamPath) {
|
||||||
|
const base = (item.save_path || '/downloads').replace(/\/+$/, '');
|
||||||
|
streamPath = `${base}/${item.name}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
videoTitle.textContent = `${previewState.activeType === 'smb' ? 'SMB Video' : 'Torrent'}: ${titleName}`;
|
videoTitle.textContent = `${previewState.activeType === 'smb' ? 'SMB Video' : 'Torrent'}: ${titleName}`;
|
||||||
|
|||||||
@@ -427,6 +427,91 @@ def ensure_smb_mounted():
|
|||||||
return False, f"Auto-mount error: {str(e)}"
|
return False, f"Auto-mount error: {str(e)}"
|
||||||
return os.path.exists(DEFAULT_SMB_MOUNT), "Mount status checked"
|
return os.path.exists(DEFAULT_SMB_MOUNT), "Mount status checked"
|
||||||
|
|
||||||
|
# ==================== Video Streaming Helper (Range 206) ====================
|
||||||
|
def resolve_stream_video_path(requested_path):
|
||||||
|
if not requested_path:
|
||||||
|
return None
|
||||||
|
ensure_smb_mounted()
|
||||||
|
|
||||||
|
# 1. Direct path check
|
||||||
|
if os.path.isfile(requested_path):
|
||||||
|
return requested_path
|
||||||
|
|
||||||
|
basename = os.path.basename(requested_path)
|
||||||
|
stem = os.path.splitext(basename)[0]
|
||||||
|
|
||||||
|
candidate_paths = [
|
||||||
|
requested_path.replace('/isolation/videos/', '/mnt/isolation/videos/'),
|
||||||
|
requested_path.replace('/isolation/', '/mnt/isolation/'),
|
||||||
|
requested_path.replace('/downloads/', '/mnt/isolation/videos/unsorted/'),
|
||||||
|
requested_path.replace('/downloads/', '/mnt/isolation/videos/videodownloader/'),
|
||||||
|
requested_path.replace('/downloads/', '/mnt/isolation/videos/'),
|
||||||
|
requested_path.replace('/downloads/', '/mnt/isolation/incomplete/'),
|
||||||
|
requested_path.replace('/downloads/', '/mnt/isolation/downloads/'),
|
||||||
|
os.path.join('/mnt/isolation/videos/unsorted', basename),
|
||||||
|
os.path.join('/mnt/isolation/videos/videodownloader', basename),
|
||||||
|
os.path.join('/mnt/isolation/incomplete', basename),
|
||||||
|
os.path.join('/mnt/isolation/downloads', basename)
|
||||||
|
]
|
||||||
|
|
||||||
|
video_exts = ('.mp4', '.mkv', '.avi', '.mov', '.webm', '.ts', '.m4v')
|
||||||
|
|
||||||
|
for p in candidate_paths:
|
||||||
|
if os.path.isfile(p):
|
||||||
|
return p
|
||||||
|
elif os.path.isdir(p):
|
||||||
|
try:
|
||||||
|
best_file = None
|
||||||
|
best_size = -1
|
||||||
|
for root, _, files in os.walk(p):
|
||||||
|
for f in files:
|
||||||
|
if f.lower().endswith(video_exts):
|
||||||
|
full = os.path.join(root, f)
|
||||||
|
sz = os.path.getsize(full)
|
||||||
|
if sz > best_size:
|
||||||
|
best_size = sz
|
||||||
|
best_file = full
|
||||||
|
if best_file:
|
||||||
|
return best_file
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 2. Check top-level directories
|
||||||
|
try:
|
||||||
|
search_dirs = [
|
||||||
|
'/mnt/isolation/videos/unsorted',
|
||||||
|
'/mnt/isolation/videos/videodownloader',
|
||||||
|
'/mnt/isolation/incomplete',
|
||||||
|
'/mnt/isolation/downloads'
|
||||||
|
]
|
||||||
|
for s_dir in search_dirs:
|
||||||
|
if os.path.exists(s_dir) and os.path.isdir(s_dir):
|
||||||
|
target = os.path.join(s_dir, basename)
|
||||||
|
if os.path.isfile(target):
|
||||||
|
return target
|
||||||
|
for entry in os.scandir(s_dir):
|
||||||
|
if entry.is_file() and (entry.name == basename or os.path.splitext(entry.name)[0] == stem):
|
||||||
|
return entry.path
|
||||||
|
elif entry.is_dir():
|
||||||
|
sub_target = os.path.join(entry.path, basename)
|
||||||
|
if os.path.isfile(sub_target):
|
||||||
|
return sub_target
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error scanning directories in streaming resolver: {e}")
|
||||||
|
|
||||||
|
# 3. Fallback search inside performer folders in /mnt/isolation/videos
|
||||||
|
try:
|
||||||
|
if os.path.exists('/mnt/isolation/videos'):
|
||||||
|
for p_entry in os.scandir('/mnt/isolation/videos'):
|
||||||
|
if p_entry.is_dir():
|
||||||
|
candidate = os.path.join(p_entry.path, basename)
|
||||||
|
if os.path.isfile(candidate):
|
||||||
|
return candidate
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
||||||
def send_cors_headers(self):
|
def send_cors_headers(self):
|
||||||
origin = self.headers.get('Origin')
|
origin = self.headers.get('Origin')
|
||||||
@@ -699,11 +784,13 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler):
|
|||||||
self.wfile.write(json.dumps({"error": f"Failed batch saving to DB: {str(e)}"}).encode('utf-8'))
|
self.wfile.write(json.dumps({"error": f"Failed batch saving to DB: {str(e)}"}).encode('utf-8'))
|
||||||
|
|
||||||
# ==================== Video Streaming Handlers (Range 206) ====================
|
# ==================== Video Streaming Handlers (Range 206) ====================
|
||||||
def handle_stream_video(self, head_only=False):
|
def handle_stream_video(self):
|
||||||
|
head_only = False
|
||||||
parsed = urllib.parse.urlparse(self.path)
|
parsed = urllib.parse.urlparse(self.path)
|
||||||
params = urllib.parse.parse_qs(parsed.query)
|
params = urllib.parse.parse_qs(parsed.query)
|
||||||
file_path = params.get('path', [''])[0]
|
raw_path = params.get('path', [''])[0]
|
||||||
|
|
||||||
|
file_path = resolve_stream_video_path(raw_path)
|
||||||
if not file_path or not os.path.exists(file_path) or not os.path.isfile(file_path):
|
if not file_path or not os.path.exists(file_path) or not os.path.isfile(file_path):
|
||||||
self.send_response(404)
|
self.send_response(404)
|
||||||
self.send_cors_headers()
|
self.send_cors_headers()
|
||||||
|
|||||||
Reference in New Issue
Block a user