diff --git a/README.md b/README.md index 42b0aea..8d5b2ad 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ -# bluray +libbluray的go封装 只支持了部分接口 +私人使用的库 只测试了其中的部分接口 +The Go wrapper for libbluray only supports a subset of the interfaces +This is a private library, and only some of the interfaces have been tested \ No newline at end of file diff --git a/bluray_test.go b/bluray_test.go new file mode 100644 index 0000000..872012d --- /dev/null +++ b/bluray_test.go @@ -0,0 +1,62 @@ +package bluray + +import ( + "encoding/json" + "fmt" + "testing" +) + +func TestGetBDVersion(t *testing.T) { + version := GetBDVersion() + if version == "" { + t.Errorf("Expected non-empty version string, got empty string") + } + fmt.Printf("libbluray version: %s\n", version) +} + +func TestBD(t *testing.T) { + db := BDOpen("path") + if db == nil { + t.Errorf("Expected non-nil DBStruct, got nil") + return + } + + info, err := db.GetDiscInfo() + if err != nil { + t.Errorf("Expected non-nil BlurayDiscInfo, got nil") + return + } + res, err := json.Marshal(info) + if err != nil { + t.Errorf("json.Marshal failed: %v", err) + return + } + fmt.Println(string(res)) + + num, err := db.GetTitles(TitlesAll, 0) + if err != nil { + t.Errorf("Expected non-nil BlurayTitle, got nil") + return + } + fmt.Printf("titles: %v\n", num) + + mainTitle, err := db.GetMainTitle() + if err != nil { + t.Errorf("Expected non-nil BlurayTitle, got nil") + return + } + fmt.Printf("main title: %v\n", mainTitle) + + tInfo, err := db.GetTitleInfo(mainTitle, 0) + if err != nil { + t.Errorf("Expected non-nil BlurayTitle, got nil") + return + } + + res, err = json.Marshal(tInfo) + if err != nil { + t.Errorf("json.Marshal failed: %v", err) + return + } + fmt.Println(string(res)) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..aa6b196 --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module git.slzz.org/gomod/bluray diff --git a/libbluray.go b/libbluray.go new file mode 100644 index 0000000..da39098 --- /dev/null +++ b/libbluray.go @@ -0,0 +1,487 @@ +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) +//} diff --git a/libbluray_type.go b/libbluray_type.go new file mode 100644 index 0000000..481f49c --- /dev/null +++ b/libbluray_type.go @@ -0,0 +1,140 @@ +package bluray + +// BlurayTitle 表示BluRay标题的信息 +type BlurayTitle struct { + Name *string // 可选的标题名称,使用首选语言 + Interactive uint8 // 如果标题是交互式的,值为1(在UI中不显示标题长度和播放位置) + Accessible uint8 // 如果允许跳转到该标题,值为1 + Hidden uint8 // 如果在播放期间不显示标题编号,值为1 + + BDJ uint8 // 0表示HDMV标题,1表示BD-J标题 + IDRef uint32 // 电影对象编号/bdjo文件编号 +} + +// BlurayDiscInfo 表示BluRay光盘的信息 +type BlurayDiscInfo struct { + BlurayDetected uint8 // 如果检测到BluRay光盘,值为1 + + // 光盘ID + DiscName *string // 可选的光盘名称,使用首选语言 + UDFVolumeID *string // 可选的UDF卷标符 + DiscID [20]byte // 光盘ID + + // HDMV / BD-J 标题 + NoMenuSupport uint8 // 如果无法通过光盘上的菜单播放该光盘,值为1 + FirstPlaySupported uint8 // 如果光盘上有First Play标题且可以播放,值为1 + TopMenuSupported uint8 // 如果光盘上有Top Menu标题且可以播放,值为1 + + NumTitles uint32 // 光盘上的标题数量,不包括"First Play"和"Top Menu" + Titles []*BlurayTitle // 标题数组,索引为标题编号1...N + FirstPlay *BlurayTitle // titles[N+1],如果光盘上不存在则为NULL + TopMenu *BlurayTitle // titles[0],如果光盘上不存在则为NULL + + NumHDMVTitles uint32 // HDMV标题数量 + NumBDJTitles uint32 // BD-J标题数量 + NumUnsupportedTitles uint32 // 不支持的标题数量 + + // BD-J 信息(仅当光盘使用BD-J时有效) + BDJDetected uint8 // 如果光盘使用BD-J,值为1 + BDJSupported uint8 // (已弃用) + LibJVMDetected uint8 // 如果找到可用的Java虚拟机,值为1 + BDJHandled uint8 // 如果找到可用的Java虚拟机和libbluray.jar,值为1 + + BDJOrgID [9]byte // (BD-J)光盘组织ID + BDJDiscID [33]byte // (BD-J)光盘ID + + // 光盘应用程序信息 + VideoFormat uint8 // 视频格式(bd_video_format_e) + FrameRate uint8 // 帧率(bd_video_rate_e) + ContentExist3D uint8 // 如果光盘上存在3D内容,值为1 + InitialOutputModePreference uint8 // 0表示2D,1表示3D + ProviderData [32]byte // 内容提供者数据 + + // AACS 信息(仅当光盘使用AACS时有效) + AACDetected uint8 // 如果光盘使用AACS加密,值为1 + LibAACSDetected uint8 // 如果找到可用的AACS解码库,值为1 + AACSHandled uint8 // 如果光盘使用支持的AACS加密,值为1 + + AACSErrorCode int // AACS错误代码(BD_AACS_) + AACSMKBV int // AACS MKB版本 + + // BD+ 信息(仅当光盘使用BD+时有效) + BDPlusDetected uint8 // 如果光盘使用BD+加密,值为1 + LibBDPlusDetected uint8 // 如果找到可用的BD+解码库,值为1 + BDPlusHandled uint8 // 如果光盘使用支持的BD+加密,值为1 + + BDPlusGen uint8 // BD+内容代码生成 + BDPlusDate uint32 // BD+内容代码发布日期 ((year<<16)|(month<<8)|day) + + // 光盘应用程序信息(libbluray > 1.2.0) + InitialDynamicRangeType uint8 // 动态范围类型(bd_dynamic_range_type_e) +} + +type BlurayMetaFile struct { + Data []byte + Size int64 +} + +type BlurayStreamInfo struct { + CodingType uint8 + Format uint8 + Rate uint8 + CharCode uint8 + Lang string + Pid uint16 + Aspect uint8 + SubpathId uint8 +} + +type BlurayClipInfo struct { + PktCount uint32 + StillMode uint8 + StillTime uint16 + VideoStreamCount uint8 + AudioStreamCount uint8 + PgStreamCount uint8 + IgStreamCount uint8 + SecAudioStreamCount uint8 + SecVideoStreamCount uint8 + VideoStreams []BlurayStreamInfo + AudioStreams []BlurayStreamInfo + PgStreams []BlurayStreamInfo + IgStreams []BlurayStreamInfo + SecAudioStreams []BlurayStreamInfo + SecVideoStreams []BlurayStreamInfo + StartTime uint64 + InTime uint64 + OutTime uint64 + ClipId uint +} + +type BlurayTitleChapter struct { + Idx uint32 + Start uint64 + Duration uint64 + Offset uint64 + ClipRef uint +} + +type BlurayTitleMark struct { + Idx uint32 + Type int + Start uint64 + Duration uint64 + Offset uint64 + ClipRef uint +} + +type BlurayTitleInfo struct { + Idx uint32 + Playlist uint32 + Duration uint64 + ClipCount uint32 + AngleCount uint8 + ChapterCount uint32 + MarkCount uint32 + Clips []BlurayClipInfo + Chapters []BlurayTitleChapter + Marks []BlurayTitleMark + MvcBaseViewRFlag uint8 +} diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..04a323b --- /dev/null +++ b/tools.go @@ -0,0 +1,29 @@ +package bluray + +/* +#include +*/ +import "C" +import "unsafe" + +func cStringToGoString(cStr *C.char) *string { + if cStr == nil { + return nil + } + str := C.GoString(cStr) + return &str +} + +func getLanguageCode(langField [4]C.uint8_t) string { + isEmpty := true + for _, b := range langField { + if b != 0 { + isEmpty = false + break + } + } + if isEmpty { + return "und" + } + return C.GoStringN((*C.char)(unsafe.Pointer(&langField[0])), 3) +}