package bluray /* #cgo pkg-config: libbluray #include #include */ import "C" import ( "fmt" "strconv" "strings" "unsafe" ) func GetBDVersion() string { var major, minor, micro C.int C.bd_get_version(&major, &minor, µ) return fmt.Sprintf("%d.%d.%d", int(major), int(minor), int(micro)) } type DBStruct struct { bd *C.BLURAY } // BDOpen 打开蓝光光盘 func BDOpen(path string) *DBStruct { cPath := C.CString(path) defer C.free(unsafe.Pointer(cPath)) return &DBStruct{bd: C.bd_open(cPath, nil)} } // GetDiscInfo 获取蓝光光盘信息 // const BlurayDiscInfo *bd_get_disc_info(BLURAY *bd); func (d *DBStruct) GetDiscInfo() (*BlurayDiscInfo, error) { cInfo := C.bd_get_disc_info(d.bd) // 调用 C 函数获取 BluRay 光盘信息 convertCToGoTitle := func(cTitle *C.BLURAY_TITLE) *BlurayTitle { if cTitle == nil { return nil } return &BlurayTitle{ Name: cStringToGoString(cTitle.name), Interactive: uint8(cTitle.interactive), Accessible: uint8(cTitle.accessible), Hidden: uint8(cTitle.hidden), BDJ: uint8(cTitle.bdj), IDRef: uint32(cTitle.id_ref), } } if cInfo == nil { return nil, fmt.Errorf("failed to get disc info") } // 转换 Titles 数组 var goTitles []*BlurayTitle if cInfo.num_titles > 0 && cInfo.titles != nil { // 遍历 C 数组,转换每个 BLURAY_TITLE cTitles := (*[1 << 30]*C.BLURAY_TITLE)(unsafe.Pointer(cInfo.titles))[:cInfo.num_titles:cInfo.num_titles] for _, cTitle := range cTitles { goTitles = append(goTitles, convertCToGoTitle(cTitle)) } } goInfo := &BlurayDiscInfo{ BlurayDetected: uint8(cInfo.bluray_detected), DiscName: cStringToGoString(cInfo.disc_name), UDFVolumeID: cStringToGoString(cInfo.udf_volume_id), DiscID: *(*[20]byte)(unsafe.Pointer(&cInfo.disc_id)), NoMenuSupport: uint8(cInfo.no_menu_support), FirstPlaySupported: uint8(cInfo.first_play_supported), TopMenuSupported: uint8(cInfo.top_menu_supported), NumTitles: uint32(cInfo.num_titles), Titles: goTitles, FirstPlay: convertCToGoTitle(cInfo.first_play), TopMenu: convertCToGoTitle(cInfo.top_menu), NumHDMVTitles: uint32(cInfo.num_hdmv_titles), NumBDJTitles: uint32(cInfo.num_bdj_titles), NumUnsupportedTitles: uint32(cInfo.num_unsupported_titles), BDJDetected: uint8(cInfo.bdj_detected), BDJSupported: uint8(cInfo.bdj_supported), LibJVMDetected: uint8(cInfo.libjvm_detected), BDJHandled: uint8(cInfo.bdj_handled), BDJOrgID: *(*[9]byte)(unsafe.Pointer(&cInfo.bdj_org_id)), BDJDiscID: *(*[33]byte)(unsafe.Pointer(&cInfo.bdj_disc_id)), VideoFormat: uint8(cInfo.video_format), FrameRate: uint8(cInfo.frame_rate), ContentExist3D: uint8(cInfo.content_exist_3D), InitialOutputModePreference: uint8(cInfo.initial_output_mode_preference), ProviderData: *(*[32]byte)(unsafe.Pointer(&cInfo.provider_data)), AACDetected: uint8(cInfo.aacs_detected), LibAACSDetected: uint8(cInfo.libaacs_detected), AACSHandled: uint8(cInfo.aacs_handled), AACSErrorCode: int(cInfo.aacs_error_code), AACSMKBV: int(cInfo.aacs_mkbv), BDPlusDetected: uint8(cInfo.bdplus_detected), LibBDPlusDetected: uint8(cInfo.libbdplus_detected), BDPlusHandled: uint8(cInfo.bdplus_handled), BDPlusGen: uint8(cInfo.bdplus_gen), BDPlusDate: uint32(cInfo.bdplus_date), InitialDynamicRangeType: uint8(cInfo.initial_dynamic_range_type), } return goInfo, nil } // GetMetaFile 获取蓝光光盘的元数据文件 const ( TitlesAll = 0 TitlesFilterDupTitle = 0x01 TitlesFilterDupClip = 0x02 TitlesRelevant = TitlesFilterDupTitle | TitlesFilterDupClip ) // GetTitles 获取标题数量(播放列表) // GetTitles Get the number of titles (playlists) on the disc func (d *DBStruct) GetTitles(flags uint8, minTitleLength uint32) (uint32, error) { numTitles := C.bd_get_titles(d.bd, C.uint8_t(flags), C.uint32_t(minTitleLength)) if numTitles == 0 { return 0, fmt.Errorf("failed to get titles or no titles found") } return uint32(numTitles), nil } // GetMainTitle 获取主标题 // GetMainTitle Get the main title func (d *DBStruct) GetMainTitle() (uint32, error) { mainTitle := C.bd_get_main_title(d.bd) if mainTitle == 0 { return 0, fmt.Errorf("failed to get main title") } return uint32(mainTitle), nil } // GetTitleInfo 从蓝光光盘获取标题信息 0为全部 // GetTitleInfo Get title info from a Blu-ray disc 0 for all func (d *DBStruct) GetTitleInfo(titleIdx uint32, angle uint) (*BlurayTitleInfo, error) { cTitleInfo := C.bd_get_title_info(d.bd, C.uint32_t(titleIdx), C.uint(angle)) if cTitleInfo == nil { return nil, fmt.Errorf("failed to get title info") } defer C.bd_free_title_info(cTitleInfo) titleInfo := &BlurayTitleInfo{ Idx: uint32(cTitleInfo.idx), Playlist: uint32(cTitleInfo.playlist), Duration: uint64(cTitleInfo.duration), ClipCount: uint32(cTitleInfo.clip_count), AngleCount: uint8(cTitleInfo.angle_count), ChapterCount: uint32(cTitleInfo.chapter_count), MarkCount: uint32(cTitleInfo.mark_count), MvcBaseViewRFlag: uint8(cTitleInfo.mvc_base_view_r_flag), } if cTitleInfo.clip_count > 0 && cTitleInfo.clips != nil { titleInfo.Clips = make([]BlurayClipInfo, cTitleInfo.clip_count) cClipsSlice := (*[1 << 30]C.BLURAY_CLIP_INFO)(unsafe.Pointer(cTitleInfo.clips))[:cTitleInfo.clip_count:cTitleInfo.clip_count] for i := 0; i < int(cTitleInfo.clip_count); i++ { cClip := cClipsSlice[i] clipIdStr := C.GoStringN(&cClip.clip_id[0], 5) clipIdStr = strings.TrimLeft(clipIdStr, "0") // Remove leading zeros if clipIdStr == "" { clipIdStr = "0" } clipId, err := strconv.ParseUint(clipIdStr, 10, 32) if err != nil { clipId = 0 } clipInfo := BlurayClipInfo{ PktCount: uint32(cClip.pkt_count), StillMode: uint8(cClip.still_mode), StillTime: uint16(cClip.still_time), VideoStreamCount: uint8(cClip.video_stream_count), AudioStreamCount: uint8(cClip.audio_stream_count), PgStreamCount: uint8(cClip.pg_stream_count), IgStreamCount: uint8(cClip.ig_stream_count), SecAudioStreamCount: uint8(cClip.sec_audio_stream_count), SecVideoStreamCount: uint8(cClip.sec_video_stream_count), StartTime: uint64(cClip.start_time), InTime: uint64(cClip.in_time), OutTime: uint64(cClip.out_time), ClipId: uint(clipId), // Assign the parsed ClipId } if cClip.video_stream_count > 0 && cClip.video_streams != nil { clipInfo.VideoStreams = make([]BlurayStreamInfo, cClip.video_stream_count) cVideoStreams := (*[1 << 30]C.BLURAY_STREAM_INFO)(unsafe.Pointer(cClip.video_streams))[:cClip.video_stream_count:cClip.video_stream_count] for j := 0; j < int(cClip.video_stream_count); j++ { cStream := cVideoStreams[j] lang := getLanguageCode(cStream.lang) streamInfo := BlurayStreamInfo{ CodingType: uint8(cStream.coding_type), Format: uint8(cStream.format), Rate: uint8(cStream.rate), CharCode: uint8(cStream.char_code), Pid: uint16(cStream.pid), Aspect: uint8(cStream.aspect), SubpathId: uint8(cStream.subpath_id), Lang: lang, } clipInfo.VideoStreams[j] = streamInfo } } if cClip.audio_stream_count > 0 && cClip.audio_streams != nil { clipInfo.AudioStreams = make([]BlurayStreamInfo, cClip.audio_stream_count) cAudioStreams := (*[1 << 30]C.BLURAY_STREAM_INFO)(unsafe.Pointer(cClip.audio_streams))[:cClip.audio_stream_count:cClip.audio_stream_count] for j := 0; j < int(cClip.audio_stream_count); j++ { cStream := cAudioStreams[j] lang := getLanguageCode(cStream.lang) streamInfo := BlurayStreamInfo{ CodingType: uint8(cStream.coding_type), Format: uint8(cStream.format), Rate: uint8(cStream.rate), CharCode: uint8(cStream.char_code), Pid: uint16(cStream.pid), Aspect: uint8(cStream.aspect), SubpathId: uint8(cStream.subpath_id), Lang: lang, } clipInfo.AudioStreams[j] = streamInfo } } if cClip.pg_stream_count > 0 && cClip.pg_streams != nil { clipInfo.PgStreams = make([]BlurayStreamInfo, cClip.pg_stream_count) cPgStreams := (*[1 << 30]C.BLURAY_STREAM_INFO)(unsafe.Pointer(cClip.pg_streams))[:cClip.pg_stream_count:cClip.pg_stream_count] for j := 0; j < int(cClip.pg_stream_count); j++ { cStream := cPgStreams[j] streamInfo := BlurayStreamInfo{ CodingType: uint8(cStream.coding_type), Format: uint8(cStream.format), Rate: uint8(cStream.rate), CharCode: uint8(cStream.char_code), Pid: uint16(cStream.pid), Aspect: uint8(cStream.aspect), SubpathId: uint8(cStream.subpath_id), Lang: C.GoStringN((*C.char)(unsafe.Pointer(&cStream.lang[0])), 3), } clipInfo.PgStreams[j] = streamInfo } } if cClip.ig_stream_count > 0 && cClip.ig_streams != nil { clipInfo.IgStreams = make([]BlurayStreamInfo, cClip.ig_stream_count) cIgStreams := (*[1 << 30]C.BLURAY_STREAM_INFO)(unsafe.Pointer(cClip.ig_streams))[:cClip.ig_stream_count:cClip.ig_stream_count] for j := 0; j < int(cClip.ig_stream_count); j++ { cStream := cIgStreams[j] streamInfo := BlurayStreamInfo{ CodingType: uint8(cStream.coding_type), Format: uint8(cStream.format), Rate: uint8(cStream.rate), CharCode: uint8(cStream.char_code), Pid: uint16(cStream.pid), Aspect: uint8(cStream.aspect), SubpathId: uint8(cStream.subpath_id), Lang: C.GoStringN((*C.char)(unsafe.Pointer(&cStream.lang[0])), 3), } clipInfo.IgStreams[j] = streamInfo } } if cClip.sec_audio_stream_count > 0 && cClip.sec_audio_streams != nil { clipInfo.SecAudioStreams = make([]BlurayStreamInfo, cClip.sec_audio_stream_count) cSecAudioStreams := (*[1 << 30]C.BLURAY_STREAM_INFO)(unsafe.Pointer(cClip.sec_audio_streams))[:cClip.sec_audio_stream_count:cClip.sec_audio_stream_count] for j := 0; j < int(cClip.sec_audio_stream_count); j++ { cStream := cSecAudioStreams[j] streamInfo := BlurayStreamInfo{ CodingType: uint8(cStream.coding_type), Format: uint8(cStream.format), Rate: uint8(cStream.rate), CharCode: uint8(cStream.char_code), Pid: uint16(cStream.pid), Aspect: uint8(cStream.aspect), SubpathId: uint8(cStream.subpath_id), Lang: C.GoStringN((*C.char)(unsafe.Pointer(&cStream.lang[0])), 3), } clipInfo.SecAudioStreams[j] = streamInfo } } if cClip.sec_video_stream_count > 0 && cClip.sec_video_streams != nil { clipInfo.SecVideoStreams = make([]BlurayStreamInfo, cClip.sec_video_stream_count) cSecVideoStreams := (*[1 << 30]C.BLURAY_STREAM_INFO)(unsafe.Pointer(cClip.sec_video_streams))[:cClip.sec_video_stream_count:cClip.sec_video_stream_count] for j := 0; j < int(cClip.sec_video_stream_count); j++ { cStream := cSecVideoStreams[j] streamInfo := BlurayStreamInfo{ CodingType: uint8(cStream.coding_type), Format: uint8(cStream.format), Rate: uint8(cStream.rate), CharCode: uint8(cStream.char_code), Pid: uint16(cStream.pid), Aspect: uint8(cStream.aspect), SubpathId: uint8(cStream.subpath_id), Lang: C.GoStringN((*C.char)(unsafe.Pointer(&cStream.lang[0])), 3), } clipInfo.SecVideoStreams[j] = streamInfo } } titleInfo.Clips[i] = clipInfo } } if cTitleInfo.chapter_count > 0 && cTitleInfo.chapters != nil { titleInfo.Chapters = make([]BlurayTitleChapter, cTitleInfo.chapter_count) cChapters := (*[1 << 30]C.BLURAY_TITLE_CHAPTER)(unsafe.Pointer(cTitleInfo.chapters))[:cTitleInfo.chapter_count:cTitleInfo.chapter_count] for i := 0; i < int(cTitleInfo.chapter_count); i++ { cChapter := cChapters[i] chapter := BlurayTitleChapter{ Idx: uint32(cChapter.idx), Start: uint64(cChapter.start), Duration: uint64(cChapter.duration), Offset: uint64(cChapter.offset), ClipRef: uint(cChapter.clip_ref), } titleInfo.Chapters[i] = chapter } } if cTitleInfo.mark_count > 0 && cTitleInfo.marks != nil { titleInfo.Marks = make([]BlurayTitleMark, cTitleInfo.mark_count) cMarks := (*[1 << 30]C.BLURAY_TITLE_MARK)(unsafe.Pointer(cTitleInfo.marks))[:cTitleInfo.mark_count:cTitleInfo.mark_count] for i := 0; i < int(cTitleInfo.mark_count); i++ { cMark := cMarks[i] mark := BlurayTitleMark{ Idx: uint32(cMark.idx), Type: int(cMark._type), // Use _type because 'type' is a reserved keyword in Go Start: uint64(cMark.start), Duration: uint64(cMark.duration), Offset: uint64(cMark.offset), ClipRef: uint(cMark.clip_ref), } titleInfo.Marks[i] = mark } } return titleInfo, nil } // SelectTitle 从 GetTitles() 创建的列表中选择标题 // [未测试] 由copilot生成的代码 // [NotTested] Code generated by copilot func (d *DBStruct) SelectTitle(titleIdx uint32) (int, error) { result := C.bd_select_title(d.bd, C.uint32_t(titleIdx)) if result != 0 { return 0, fmt.Errorf("failed to select title") } return int(result), nil } // SelectPlaylist 选择播放列表 // [未测试] 由copilot生成的代码 // [NotTested] Code generated by copilot func (d *DBStruct) SelectPlaylist(playlistIdx uint32) (int, error) { result := C.bd_select_playlist(d.bd, C.uint32_t(playlistIdx)) if result != 0 { return 0, fmt.Errorf("failed to select playlist") } return int(result), nil } // GetCurrentTitle 获取当前标题 // [未测试] 由copilot生成的代码 // [NotTested] Code generated by copilot func (d *DBStruct) GetCurrentTitle() (uint32, error) { result := C.bd_get_current_title(d.bd) if result == 0 { return 0, fmt.Errorf("failed to get current title") } return uint32(result), nil } // Read Read from currently selected title file, decrypt if possible // [未测试] 由copilot生成的代码 // [NotTested] Code generated by copilot func (d *DBStruct) Read() (int, error) { result := C.bd_read(d.bd) if result != 0 { return 0, fmt.Errorf("failed to read") } return int(result), nil } //// Seek Seek to a position in the currently selected title file //// [未测试] 由copilot生成的代码 //// [NotTested] Code generated by copilot //func (d *DBStruct) Seek(pos uint64) (int64, error) { // result := C.bd_seek(d.bd, C.uint64_t(pos)) // return int64(result), nil //} // //// SeekTime Seek to a time in the currently selected title file //// [未测试] 由copilot生成的代码 //// [NotTested] Code generated by copilot //func (d *DBStruct) SeekTime(tick uint64) (int64, error) { // result := C.bd_seek_time(d.bd, C.uint64_t(tick)) // return int64(result), nil //} // //// SeekChapter Seek to a chapter in the currently selected title file //// [未测试] 由copilot生成的代码 //// [NotTested] Code generated by copilot //func (d *DBStruct) SeekChapter(chapter uint) (int64, error) { // result := C.bd_seek_chapter(d.bd, C.uint(chapter)) // return int64(result), nil //} // //// SeekMark Seek to a mark in the currently selected title file //// [未测试] 由copilot生成的代码 //// [NotTested] Code generated by copilot //func (d *DBStruct) SeekMark(mark uint) (int64, error) { // result := C.bd_seek_mark(d.bd, C.uint(mark)) // return int64(result), nil //} // //// SeekPlayitem Seek to a playitem in the currently selected title file //// [未测试] 由copilot生成的代码 //// [NotTested] Code generated by copilot //func (d *DBStruct) SeekPlayitem(clipRef uint) (int64, error) { // result := C.bd_seek_playitem(d.bd, C.uint(clipRef)) // return int64(result), nil //} // //// SelectAngle Set the angle to play //// [未测试] 由copilot生成的代码 //// [NotTested] Code generated by copilot //func (d *DBStruct) SelectAngle(angle uint) (int, error) { // result := C.bd_select_angle(d.bd, C.uint(angle)) // if result != 0 { // return 0, fmt.Errorf("failed to select angle") // } // return int(result), nil //} // //// SeamlessAngleChange Change the angle without stopping playback //// [未测试] 由copilot生成的代码 //// [NotTested] Code generated by copilot //func (d *DBStruct) SeamlessAngleChange(angle uint) { // C.bd_seamless_angle_change(d.bd, C.uint(angle)) //} // //// SelectStream ///** // * // * Select stream (PG / TextST track) // * // * When playing with on-disc menus: // * // * Stream selection is controlled by on-disc menus. // * If user can change stream selection also in player GUI, this function // * should be used to keep on-disc menus in sync with player GUI. // * // * When playing the disc without on-disc menus: // * // * Initial stream selection is done using preferred language settings. // * This function can be used to override automatic stream selection. // * Without on-disc menus selecting the stream is useful only when using // * libbluray internal decoders or the stream is stored in a sub-path. // * // * @param bd BLURAY object // * @param stream_type BLURAY_AUDIO_STREAM or BLURAY_PG_TEXTST_STREAM // * @param stream_id stream number (1..N) // * @param enable_flag set to 0 to disable streams of this type // */ //func (d *DBStruct) SelectStream(streamType uint32, streamID uint32, enableFlag uint32) { // C.bd_select_stream(d.bd, C.uint32_t(streamType), C.uint32_t(streamID), C.uint32_t(enableFlag)) //} // //// Close 关闭蓝光光盘 //func (d *DBStruct) Close() { // C.bd_close(d.bd) //}