360mpgui V1.5.0.0 • Ad-Free
| Module | Functionality | |--------|----------------| | File Browser | Browse folders, show thumbnails, list all images/videos | | Metadata Viewer | Shows dimensions, size, type, FPS for videos | | 360° Panorama Viewer | Drag to rotate yaw/pitch, real-time equirectangular preview | | Cubemap Converter | Convert selected images to 6-face cubemap (right/left/up/down/front/back) | | Video Frame Extractor | Extract frames from 360° video at custom intervals | | Persistent Settings | Remembers last opened folder | | Dark UI | Professional dark theme with splitter panels |
Under the hood, 360mpgui relies on GPAC’s MP4Box. Version 1.5.0.0 ships with a more recent build of MP4Box, bringing:
class MainWindow(QMainWindow): def init(self): super().init() self.setWindowTitle("360mpgui v1.5.0.0 - 360° Media Manager") self.setGeometry(100, 100, 1300, 800) self.setStyleSheet(""" QMainWindow background-color: #2d2d2d; QLabel, QListWidget, QTextEdit color: #f0f0f0; QPushButton background-color: #3c3c3c; color: white; border: none; padding: 5px; border-radius: 3px; QPushButton:hover background-color: #505050; QListWidget::item:selected background-color: #0078d7; QTabWidget::pane border: 1px solid #3c3c3c; background: #252526; QTabBar::tab background: #2d2d2d; color: #ccc; padding: 6px; QTabBar::tab:selected background: #0078d7; """)
self.current_files = [] # list of file paths in current folder
self.current_media = None # numpy array for preview
self.init_ui()
self.load_last_folder()
def init_ui(self):
# Central widget with splitter
central = QWidget()
self.setCentralWidget(central)
main_layout = QHBoxLayout(central)
main_layout.setContentsMargins(5,5,5,5)
splitter = QSplitter(Qt.Horizontal)
main_layout.addWidget(splitter)
# Left panel: file browser & metadata
left_panel = QWidget()
left_layout = QVBoxLayout(left_panel)
self.folder_edit = QLineEdit()
self.folder_edit.setPlaceholderText("Folder path...")
self.browse_btn = QPushButton("Browse")
self.browse_btn.clicked.connect(self.browse_folder)
top_row = QHBoxLayout()
top_row.addWidget(self.folder_edit)
top_row.addWidget(self.browse_btn)
left_layout.addLayout(top_row)
self.file_list = QListWidget()
self.file_list.itemClicked.connect(self.on_file_selected)
left_layout.addWidget(QLabel("Media Files:"))
left_layout.addWidget(self.file_list)
self.meta_text = QTextEdit()
self.meta_text.setReadOnly(True)
self.meta_text.setMaximumHeight(200)
left_layout.addWidget(QLabel("Metadata:"))
left_layout.addWidget(self.meta_text)
splitter.addWidget(left_panel)
# Right panel: tabs for Viewer, Converter, Video Tools
right_tabs = QTabWidget()
splitter.addWidget(right_tabs)
splitter.setSizes([400, 900])
# Tab 1: Panorama Viewer
viewer_tab = QWidget()
viewer_layout = QVBoxLayout(viewer_tab)
self.panorama = PanoramaViewer()
viewer_layout.addWidget(self.panorama)
info_lbl = QLabel("🖱️ Drag mouse to rotate 360° panorama")
info_lbl.setAlignment(Qt.AlignCenter)
viewer_layout.addWidget(info_lbl)
right_tabs.addTab(viewer_tab, "360° Viewer")
# Tab 2: Batch Converter (Equirectangular -> Cubemap)
conv_tab = QWidget()
conv_layout = QVBoxLayout(conv_tab)
conv_layout.addWidget(QLabel("Convert equirectangular images to cubemap faces"))
self.conv_list = QListWidget()
self.conv_list.setSelectionMode(QAbstractItemView.MultiSelection)
conv_layout.addWidget(self.conv_list)
self.output_dir_edit = QLineEdit()
self.output_dir_edit.setPlaceholderText("Output directory for cubemaps")
self.browse_output_btn = QPushButton("Select Output")
self.browse_output_btn.clicked.connect(self.select_output_dir)
row = QHBoxLayout()
row.addWidget(self.output_dir_edit)
row.addWidget(self.browse_output_btn)
conv_layout.addLayout(row)
self.cube_size = QSpinBox()
self.cube_size.setRange(256, 2048)
self.cube_size.setValue(512)
conv_layout.addWidget(QLabel("Cubemap face size (px):"))
conv_layout.addWidget(self.cube_size)
self.convert_btn = QPushButton("Convert Selected to Cubemap")
self.convert_btn.clicked.connect(self.start_conversion)
conv_layout.addWidget(self.convert_btn)
self.conv_progress = QProgressBar()
conv_layout.addWidget(self.conv_progress)
right_tabs.addTab(conv_tab, "Cubemap Converter")
# Tab 3: Video Tools (extract frames)
video_tab = QWidget()
video_layout = QVBoxLayout(video_tab)
video_layout.addWidget(QLabel("Extract frames from 360° video"))
self.video_file_edit = QLineEdit()
self.video_file_edit.setPlaceholderText("Select video file")
self.select_video_btn = QPushButton("Browse Video")
self.select_video_btn.clicked.connect(self.select_video_file)
row2 = QHBoxLayout()
row2.addWidget(self.video_file_edit)
row2.addWidget(self.select_video_btn)
video_layout.addLayout(row2)
self.frame_interval = QSpinBox()
self.frame_interval.setRange(1, 300)
self.frame_interval.setValue(30)
video_layout.addWidget(QLabel("Extract every N frames:"))
video_layout.addWidget(self.frame_interval)
self.extract_btn = QPushButton("Extract Frames")
self.extract_btn.clicked.connect(self.extract_frames)
video_layout.addWidget(self.extract_btn)
self.video_progress = QProgressBar()
video_layout.addWidget(self.video_progress)
right_tabs.addTab(video_tab, "Video Extractor")
# Status bar
self.statusBar().showMessage("Ready")
def load_last_folder(self):
config_file = Path.home() / ".360mpgui_config.json"
if config_file.exists():
try:
with open(config_file, "r") as f:
cfg = json.load(f)
last = cfg.get("last_folder", "")
if os.path.isdir(last):
self.folder_edit.setText(last)
self.load_folder(last)
except:
pass
def save_config(self):
cfg = "last_folder": self.folder_edit.text()
try:
with open(Path.home() / ".360mpgui_config.json", "w") as f:
json.dump(cfg, f)
except:
pass
def browse_folder(self):
folder = QFileDialog.getExistingDirectory(self, "Select Media Folder")
if folder:
self.folder_edit.setText(folder)
self.load_folder(folder)
def load_folder(self, folder):
self.current_files = []
self.file_list.clear()
self.conv_list.clear()
for ext in SUPPORTED_IMG | SUPPORTED_VID:
for f in Path(folder).glob(f"*ext"):
self.current_files.append(str(f))
self.file_list.addItem(f.name)
self.conv_list.addItem(f.name)
self.statusBar().showMessage(f"Loaded len(self.current_files) media files")
self.save_config()
def on_file_selected(self, item):
idx = self.file_list.row(item)
if idx < len(self.current_files):
path = self.current_files[idx]
meta = get_media_metadata(path)
meta_str = "\n".join([f"k: v" for k, v in meta.items()])
self.meta_text.setText(meta_str)
# Load for panorama viewer if image
ext = Path(path).suffix.lower()
if ext in SUPPORTED_IMG:
try:
img = cv2.imread(path)
if img is not None:
self.current_media = img
self.panorama.set_image(img)
self.statusBar().showMessage(f"Loaded 360° image: Path(path).name")
except Exception as e:
self.statusBar().showMessage(f"Error loading image: e")
elif ext in SUPPORTED_VID:
self.statusBar().showMessage(f"Video selected: use Video Extractor tab")
def select_output_dir(self):
dir_ = QFileDialog.getExistingDirectory(self, "Output for cubemaps")
if dir_:
self.output_dir_edit.setText(dir_)
def start_conversion(self):
selected = self.conv_list.selectedItems()
if not selected or not self.output_dir_edit.text():
QMessageBox.warning(self, "Error", "Select images and output folder")
return
indices = [self.conv_list.row(item) for item in selected]
files = [self.current_files[i] for i in indices]
self.convert_btn.setEnabled(False)
self.conv_progress.setMaximum(len(files))
self.conv_progress.setValue(0)
threading.Thread(target=self.batch_convert, args=(files,), daemon=True).start()
def batch_convert(self, files):
out_dir = self.output_dir_edit.text()
for i, fpath in enumerate(files):
img = cv2.imread(fpath)
if img is None:
continue
cubes = equirect_to_cubemap(img, self.cube_size.value())
name = Path(fpath).stem
face_dir = Path(out_dir) / name
face_dir.mkdir(exist_ok=True)
for face, data in cubes.items():
cv2.imwrite(str(face_dir / f"face.jpg"), data)
QMetaObject.invokeMethod(self.conv_progress, "setValue", Qt.QueuedConnection, Q_ARG(int, i+1))
QMetaObject.invokeMethod(self, "conversion_done", Qt.QueuedConnection)
def conversion_done(self):
self.convert_btn.setEnabled(True)
QMessageBox.information(self, "Done", "Cubemap conversion finished.")
self.statusBar().showMessage("Conversion completed")
def select_video_file(self):
fname, _ = QFileDialog.getOpenFileName(self, "Select 360° Video", "", "Video Files (*.mp4 *.mov *.avi *.mkv)")
if fname:
self.video_file_edit.setText(fname)
def extract_frames(self):
video_path = self.video_file_edit.text()
if not video_path or not os.path.exists(video_path):
QMessageBox.warning(self, "Error", "Select valid video file")
return
out_dir = QFileDialog.getExistingDirectory(self, "Select output folder for frames")
if not out_dir:
return
self.extract_btn.setEnabled(False)
self.video_progress.setValue(0)
threading.Thread(target=self.do_extract_frames, args=(video_path, out_dir), daemon=True).start()
def do_extract_frames(self, video_path, out_dir):
cap = cv2.VideoCapture(video_path)
total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
interval = self.frame_interval.value()
count = 0
saved = 0
while True:
ret, frame = cap.read()
if not ret:
break
if count % interval == 0:
out_path = os.path.join(out_dir, f"frame_saved:06d.jpg")
cv2.imwrite(out_path, frame)
saved += 1
count += 1
if total > 0:
QMetaObject.invokeMethod(self.video_progress, "setValue", Qt.QueuedConnection, Q_ARG(int, int(count/total*100)))
cap.release()
QMetaObject.invokeMethod(self, "extraction_done", Qt.QueuedConnection, Q_ARG(int, saved))
def extraction_done(self, saved):
self.extract_btn.setEnabled(True)
QMessageBox.information(self, "Done", f"Extracted saved frames.")
self.statusBar().showMessage("Video extraction finished")
Tip: For drives with thousands of bad sectors, run
ERASE WAITbefore remap to stabilize the surface. 360mpgui v1.5.0.0
Rating: 8.9/10
360mpgui v1.5.0.0 is more than just a version number—it is a milestone in storage diagnostics. Offering a stable, feature-rich interface for HDD repair, it empowers users to extend the life of mechanical drives, recover critical data, and understand the low-level anatomy of their storage devices.
Whether you are clamping a failing laptop drive for one final backup or auditing the health of a server farm, mastering 360mpgui v1.5.0.0 gives you a level of control that commercial software often hides behind a paywall. Under the hood, 360mpgui relies on GPAC’s MP4Box
Remember: With great power comes great responsibility. Always back up your data before running destructive tests, and respect the hardware boundaries that keep our digital lives intact.
Warning: Many third-party websites bundle malware with older diagnostic tools. Follow these steps to obtain a clean copy of 360mpgui v1.5.0.0:
Direct link pattern (conceptual): https://archive.org/download/hdd-utilities/360mpgui_v1.5.0.0.zip Monitor the graph: Green blocks are good; orange/red
After download, scan the ZIP file with Windows Defender or Malwarebytes before extraction.
360mpgui v1.5.0.0 should never be used for:
However, it is fully legal to use on your own hardware for diagnostics, repair, or educational research. Many open-source projects reference the 360mpgui driver as a learning example for Windows ATA passthrough.