360mpgui V1.5.0.0 «PRO × Review»
The software reads Self-Monitoring, Analysis, and Reporting Technology (S.M.A.R.T.) data from ATA/SATA drives. Version 1.5.0.0 introduced more accurate raw value conversion for newer SSD and HDD models, including:
class PanoramaViewer(QWidget): def init(self, parent=None): super().init(parent) self.image = None self.pixmap = None self.angle_x = 0 # yaw self.angle_y = 0 # pitch self.last_pos = None self.setMinimumSize(400, 300) self.setStyleSheet("background-color: #1e1e1e; border: 1px solid #3c3c3c;")
def set_image(self, img_array):
"""Set new equirectangular image."""
self.image = img_array
self.angle_x = 0
self.angle_y = 0
self.update_view()
def update_view(self):
if self.image is None:
return
h, w = self.image.shape[:2]
# simulate simple 360° pan by cropping a shifted region
crop_w = min(w, int(w * 0.7))
crop_h = min(h, int(h * 0.7))
start_x = int((self.angle_x / 360.0) * w) % w
# pitch clamp
pitch_clamp = max(-80, min(80, self.angle_y))
start_y = int(((pitch_clamp + 90) / 180.0) * h) % h
# wrap horizontally
if start_x + crop_w <= w:
cropped = self.image[start_y:start_y+crop_h, start_x:start_x+crop_w]
else:
part1 = self.image[start_y:start_y+crop_h, start_x:w]
part2 = self.image[start_y:start_y+crop_h, 0:(start_x+crop_w)-w]
cropped = np.hstack((part1, part2))
# resize to widget size
img = cv2.resize(cropped, (self.width(), self.height()))
rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
h, w, ch = rgb.shape
bytes_per_line = ch * w
qt_img = QImage(rgb.data, w, h, bytes_per_line, QImage.Format_RGB888)
self.pixmap = QPixmap.fromImage(qt_img)
self.update()
def paintEvent(self, event):
if self.pixmap:
painter = QPainter(self)
painter.drawPixmap(0, 0, self.pixmap)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.last_pos = event.pos()
def mouseMoveEvent(self, event):
if self.last_pos is not None:
dx = event.x() - self.last_pos.x()
dy = event.y() - self.last_pos.y()
self.angle_x = (self.angle_x + dx) % 360
self.angle_y = max(-90, min(90, self.angle_y + dy))
self.update_view()
self.last_pos = event.pos()
def mouseReleaseEvent(self, event):
self.last_pos = None
def resizeEvent(self, event):
if self.image is not None:
self.update_view()
For gamers who grew up in the golden era of the Xbox 360, the desire to preserve and explore those classic titles never fades. Whether you are a retro enthusiast, a developer, or someone trying to back up your digital library, having the right tools for the job is essential. 360mpgui v1.5.0.0
Today, we are taking a closer look at a staple tool in the console modding scene: 360mpGUI v1.5.0.0.
If you’ve ever struggled with ISO formats, XEX files, or simply wanted to organize your Xbox 360 library on your PC, this utility might just be the Swiss Army Knife you’ve been looking for. For gamers who grew up in the golden
In the rapidly evolving world of digital multimedia tools, few utilities strike a balance between raw functionality and user-friendly design. One such tool that has garnered attention within niche communities is 360mpgui. Specifically, version 1.5.0.0 marks a significant milestone for this software. Whether you are a video editor, a streaming enthusiast, or simply a power user looking to handle 360-degree video content, understanding the capabilities of 360mpgui v1.5.0.0 is essential.
This article provides an exhaustive review of 360mpgui v1.5.0.0, covering its core features, installation process, use cases, performance benchmarks, and comparisons with other tools. class MainWindow(QMainWindow): def init (self): super()
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")
In an age of terabyte-sized modern games, the Xbox 360 library represents a time when games were "complete" on the disc. However, the physical media is rotting. Discs degrade over time, making digital preservation critical.
360mpGUI v1.5.0.0 acts as a preservationist's tool. By allowing users to convert their aging physical discs into digital formats that can be stored on hard drives, it ensures that titles like Halo 3, Gears of War, and Lost Odyssey remain playable for decades to come.
if name == "main": app = QApplication(sys.argv) app.setStyle("Fusion") window = MainWindow() window.show() sys.exit(app.exec_())