diff --git a/core/dlna_controller.py b/core/dlna_controller.py index 900f40c..3f87fc3 100644 --- a/core/dlna_controller.py +++ b/core/dlna_controller.py @@ -132,12 +132,13 @@ class DLNAController: # ================== AVTransport 服务方法 ================== - def set_av_transport_uri(self, media_url, metadata=""): + def set_av_transport_uri(self, media_url, title = "", metadata=""): """ 设置播放URI Args: media_url: 媒体文件的URL + title: 媒体文件名称 metadata: 媒体元数据(可选) Returns: @@ -146,16 +147,27 @@ class DLNAController: if not self.av_transport_url: return False + if title and not metadata: + # 转义title中的XML特殊字符 + title_escaped = title.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') + # 构建简单的DIDL-Lite元数据 + metadata = f""" + + {title_escaped} + + """ + # 转义XML特殊字符 media_url_escaped = media_url.replace('&', '&').replace('<', '<').replace('>', '>') media_url_escaped = urllib.parse.quote(media_url_escaped,safe=':/') metadata_escaped = metadata.replace('&', '&').replace('<', '<').replace('>', '>') soap_body = f""" - 0 - {media_url_escaped} - {metadata_escaped} -""" + 0 + {media_url_escaped} + {metadata_escaped} + """ action = "urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI" success, response = self._send_soap_request(self.av_transport_url, action, soap_body) @@ -178,9 +190,9 @@ class DLNAController: return False soap_body = f""" - 0 - {speed} -""" + 0 + {speed} + """ action = "urn:schemas-upnp-org:service:AVTransport:1#Play" success, response = self._send_soap_request(self.av_transport_url, action, soap_body) @@ -198,8 +210,8 @@ class DLNAController: return False soap_body = """ - 0 -""" + 0 + """ action = "urn:schemas-upnp-org:service:AVTransport:1#Pause" success, response = self._send_soap_request(self.av_transport_url, action, soap_body) @@ -217,8 +229,8 @@ class DLNAController: return False soap_body = """ - 0 -""" + 0 + """ action = "urn:schemas-upnp-org:service:AVTransport:1#Stop" success, response = self._send_soap_request(self.av_transport_url, action, soap_body) @@ -242,10 +254,10 @@ class DLNAController: target_escaped = target.replace('&', '&').replace('<', '<').replace('>', '>') soap_body = f""" - 0 - {unit} - {target_escaped} -""" + 0 + {unit} + {target_escaped} + """ action = "urn:schemas-upnp-org:service:AVTransport:1#Seek" success, response = self._send_soap_request(self.av_transport_url, action, soap_body) @@ -267,8 +279,8 @@ class DLNAController: return None soap_body = """ - 0 -""" + 0 + """ action = "urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo" success, response = self._send_soap_request(self.av_transport_url, action, soap_body) @@ -321,8 +333,8 @@ class DLNAController: return None soap_body = """ - 0 -""" + 0 + """ action = "urn:schemas-upnp-org:service:AVTransport:1#GetPositionInfo" success, response = self._send_soap_request(self.av_transport_url, action, soap_body) @@ -375,10 +387,10 @@ class DLNAController: volume = max(0, min(100, int(volume))) soap_body = f""" - 0 - {channel} - {volume} -""" + 0 + {channel} + {volume} + """ action = "urn:schemas-upnp-org:service:RenderingControl:1#SetVolume" success, response = self._send_soap_request(self.rendering_control_url, action, soap_body) @@ -399,9 +411,9 @@ class DLNAController: return None soap_body = f""" - 0 - {channel} -""" + 0 + {channel} + """ action = "urn:schemas-upnp-org:service:RenderingControl:1#GetVolume" success, response = self._send_soap_request(self.rendering_control_url, action, soap_body) @@ -444,10 +456,10 @@ class DLNAController: desired_mute = "1" if mute else "0" soap_body = f""" - 0 - {channel} - {desired_mute} -""" + 0 + {channel} + {desired_mute} + """ action = "urn:schemas-upnp-org:service:RenderingControl:1#SetMute" success, response = self._send_soap_request(self.rendering_control_url, action, soap_body) @@ -468,9 +480,9 @@ class DLNAController: return None soap_body = f""" - 0 - {channel} -""" + 0 + {channel} + """ action = "urn:schemas-upnp-org:service:RenderingControl:1#GetMute" success, response = self._send_soap_request(self.rendering_control_url, action, soap_body) @@ -512,7 +524,7 @@ class DLNAController: return None soap_body = """ -""" + """ action = "urn:schemas-upnp-org:service:ConnectionManager:1#GetProtocolInfo" success, response = self._send_soap_request(self.connection_manager_url, action, soap_body) @@ -540,119 +552,3 @@ class DLNAController: except ET.ParseError: return None - # ================== 高级方法 ================== - - def play_media(self, media_url, metadata="", wait=1): - """ - 播放媒体文件(组合操作) - - Args: - media_url: 媒体文件URL - metadata: 媒体元数据 - wait: 设置URI后的等待时间(秒) - - Returns: - bool: 是否成功 - """ - import time - - if self.set_av_transport_uri(media_url, metadata): - time.sleep(wait) - return self.play() - return False - - def get_device_status(self): - """ - 获取设备完整状态 - - Returns: - dict: 设备状态信息 - """ - status = { - 'device_info': { - 'friendly_name': self.device_info.get('friendly_name', '未知'), - 'ip': self.device_info.get('ip', '未知'), - 'location': self.device_info.get('location', '未知') - }, - 'transport_info': self.get_transport_info(), - 'position_info': self.get_position_info(), - 'volume': self.get_volume(), - 'mute': self.get_mute(), - 'protocol_info': self.get_protocol_info(), - 'services': { - 'av_transport': self.av_transport_url is not None, - 'rendering_control': self.rendering_control_url is not None, - 'connection_manager': self.connection_manager_url is not None - } - } - - return status - - def get_supported_formats(self): - """ - 获取设备支持的媒体格式 - - Returns: - list: 支持的格式列表 - """ - protocol_info = self.get_protocol_info() - if not protocol_info or 'sink' not in protocol_info: - return [] - - # 解析sink字段,提取支持的格式 - sink_info = protocol_info['sink'] - formats = [] - - # DLNA格式通常是逗号分隔的 - for fmt in sink_info.split(','): - fmt = fmt.strip() - if fmt: - formats.append(fmt) - - return formats - - def can_play_format(self, media_url): - """ - 检查设备是否可能支持指定格式 - - Args: - media_url: 媒体URL - - Returns: - bool: 是否可能支持 - """ - import mimetypes - - # 获取文件扩展名 - if '.' in media_url: - ext = media_url.split('.')[-1].lower() - - # 常见扩展名到MIME类型的映射 - ext_to_mime = { - 'mp4': 'video/mp4', - 'mp3': 'audio/mp3', - 'm4a': 'audio/mp4', - 'wav': 'audio/wav', - 'flac': 'audio/flac', - 'mkv': 'video/x-matroska', - 'avi': 'video/x-msvideo', - 'mov': 'video/quicktime', - 'wmv': 'video/x-ms-wmv', - 'flv': 'video/x-flv', - 'webm': 'video/webm', - 'ogg': 'audio/ogg', - 'oga': 'audio/ogg', - 'ogv': 'video/ogg', - 'm3u8': 'application/x-mpegURL', - 'mpd': 'application/dash+xml' - } - - mime_type = ext_to_mime.get(ext) - if mime_type: - # 检查设备是否支持该MIME类型 - supported_formats = self.get_supported_formats() - for fmt in supported_formats: - if mime_type in fmt: - return True - - return False \ No newline at end of file diff --git a/ui/main_window.py b/ui/main_window.py index e3e49ec..b11b8b9 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -43,8 +43,10 @@ class MainWindow(QMainWindow): # 获取图标路径(支持打包和开发环境) if getattr(sys, 'frozen', False): - # 打包后的exe环境 - base_path = sys._MEIPASS + # pyInstaller打包后的exe环境 + # base_path = sys._MEIPASS + # Nuitka打包后 + base_path = os.path.dirname(sys.executable) else: # 开发环境 base_path = os.path.abspath(".") @@ -707,7 +709,7 @@ class MainWindow(QMainWindow): self.log_message("设置播放地址", file_url) # 设置播放URI并开始播放 - if self.dlna_controller.set_av_transport_uri(file_url): + if self.dlna_controller.set_av_transport_uri(file_url, file_name): self.dlna_controller.play() self.start_btn.setEnabled(False) self.pause_btn.setEnabled(True) @@ -740,7 +742,7 @@ class MainWindow(QMainWindow): self.log_message("设置服务路径", "成功" if dir_bool else "失败") file_url = f"http://{ip_text}/{file_name}" - if self.dlna_controller.set_av_transport_uri(file_url): + if self.dlna_controller.set_av_transport_uri(file_url, file_name): self.dlna_controller.play() self.start_btn.setEnabled(False) self.pause_btn.setEnabled(True) @@ -758,27 +760,27 @@ class MainWindow(QMainWindow): def pause_casting(self): """暂停投屏""" if self.dlna_controller: - if self.dlna_controller.pause(): - self.is_paused = True # 标记为暂停状态 + self.dlna_controller.pause() + self.is_paused = True # 标记为暂停状态 - self.start_btn.setText("继续播放") - self.start_btn.setEnabled(True) - self.pause_btn.setEnabled(False) + self.start_btn.setText("继续播放") + self.start_btn.setEnabled(True) + self.pause_btn.setEnabled(False) - self.log_message("投屏", "已暂停播放") + self.log_message("投屏", "已暂停播放") def stop_casting(self): """停止投屏""" if self.dlna_controller: - if self.dlna_controller.stop(): - self.is_paused = False # 重置暂停状态 + self.dlna_controller.stop() + self.is_paused = False # 重置暂停状态 - self.start_btn.setText("开始投屏") - self.start_btn.setEnabled(True) - self.pause_btn.setEnabled(False) - self.stop_btn.setEnabled(False) + self.start_btn.setText("开始投屏") + self.start_btn.setEnabled(True) + self.pause_btn.setEnabled(False) + self.stop_btn.setEnabled(False) - self.log_message("投屏", "已停止播放") + self.log_message("投屏", "已停止播放") def volume_changed(self, value): """音量改变"""