From 60d315c6f613105719ed65a0a8fbd9eecaa8aa3c Mon Sep 17 00:00:00 2001 From: Sergey Petrov Date: Wed, 9 Apr 2025 16:30:19 +0500 Subject: [PATCH] Refactoring. --- reader/cmd/main.go | 12 +- reader/internal/processor/file.go | 238 --------------- reader/internal/processor/proc.go | 6 +- reader/internal/processor/time.go | 47 --- reader/internal/unpacker/proc.go | 12 +- reader/internal/unpacker/unpack.go | 109 ------- writer/pkg/storage/file.go | 454 +++++++++++++++++++++++++++++ 7 files changed, 469 insertions(+), 409 deletions(-) delete mode 100644 reader/internal/processor/file.go delete mode 100644 reader/internal/processor/time.go delete mode 100644 reader/internal/unpacker/unpack.go diff --git a/reader/cmd/main.go b/reader/cmd/main.go index 6d9cc0e..f9848b6 100644 --- a/reader/cmd/main.go +++ b/reader/cmd/main.go @@ -13,9 +13,13 @@ import ( func main() { //port := 8080 // - //http.HandleFunc("GET /download", handlers.Download) // example request: {"date": "07-03-2025", "start_time": "16-43", "end_time": "16-44"} - //http.HandleFunc("GET /hls/", handlers.HLS) - //http.HandleFunc("GET /vods", handlers.ListVodsHandler) + //http.HandleFunc("GET /api/v1/download/", handlers.Download) // example request: {"date": "07-03-2025", "start_time": "16-43", "end_time": "16-44"} + //http.HandleFunc("GET /api/v1/hls/", handlers.HLS) + //http.HandleFunc("GET /api/v1/vods/", handlers.ListVodsHandler) + //http.HandleFunc("GET /api/v1/vods/{id}/", handlers.ConfigVodsHandler) + //http.HandleFunc("DELETE /api/v1/vods/{id}/", handlers.DelVodsHandler) + //http.HandleFunc("GET /api/v1/vods/{id}/files/", handlers.ListFilesVodsHandler) + //http.HandleFunc("GET /api/v1/vods/{id}/{res}/{file}", handlers.FileVodsHandler) // //log.Println("Starting server on:") //log.Printf("Serving on HTTP port: %d\n", port) @@ -33,6 +37,6 @@ func main() { err = unpacker.CreateVideo() if err != nil { - logger.Log.Error("Failed to create flow", zap.Error(err)) + logger.Log.Error("failed to create flow", zap.Error(err)) } } diff --git a/reader/internal/processor/file.go b/reader/internal/processor/file.go deleted file mode 100644 index 0bc4a4e..0000000 --- a/reader/internal/processor/file.go +++ /dev/null @@ -1,238 +0,0 @@ -package processor - -import ( - "fmt" - "os" - "os/exec" - "strconv" - "strings" - "time" -) - -// partitionTime convert numbers from type STRING to type INT. -func partitionTime(time string) (startHour int, startMinute int, err error) { - // Part hours and minutes. - s := []byte(time) - h := []byte{s[0], s[1]} - m := []byte{s[3], s[4]} - - // Transforms hours and minutes into INTs. - startHour, err = strconv.Atoi(string(h)) - if err != nil { - return 0, 0, err - } - startMinute, err = strconv.Atoi(string(m)) - if err != nil { - return 0, 0, err - } - - return startHour, startMinute, nil -} - -// createTXT collects filenames into TXT file. -func createTXT(path, date, startTime, endTime string) (string, error) { - //fileNames := make([]string, 0) - - fileNameTXT := startTime + "_" + endTime + ".txt" - f, err := os.Create(path + fileNameTXT) - if err != nil { - return "", fmt.Errorf("create file error: %s", err.Error()) - } - defer f.Close() - - // Read the directory. - dirEntry, err := os.ReadDir(path) - if err != nil { - return "", fmt.Errorf("read directory error: %s", err.Error()) - } - - // Calculate start time and end time of the required fragment. - startHour, startMinute, endHour, endMinute := calcNeededTime(startTime, endTime) - - for i := startHour; i <= endHour; i++ { - for j := startMinute; j < endMinute; j++ { // exclude the last minute as endMinute is the final time - for _, entry := range dirEntry { - if strings.Contains(entry.Name(), strconv.Itoa(i)+"-"+strconv.Itoa(j)+"-00"+"_"+date) { - _, err = f.WriteString("file '" + entry.Name() + "'\n") - if err != nil { - return "", fmt.Errorf("write file error: %s", err.Error()) - } - } - } - } - } - - return fileNameTXT, nil -} - -// mergeFiles merges the collected files into one MP4 file. -func mergeFiles(path, fileNamesTXT string) (string, error) { - fileNameRes := time.Now().Format("15-04-05") + "_video.mp4" - - cmd := exec.Command("ffmpeg", - "-f", "concat", - "-safe", "0", - "-fflags", "+genpts", - "-i", path+fileNamesTXT, - "-c", "copy", - path+fileNameRes) - - err := cmd.Run() - if err != nil { - return "", fmt.Errorf("merge files error: %s", err.Error()) - } - - return fileNameRes, nil -} - -///////////////////////////////////////////////////////////////////////////////////// - -//// Функция copyFile находит файл в репозитории и создает его копию. -//func copyFiles(path string, filename []string) (filenameCopied []string, err error) { -// for i := 0; i < len(filename); i++ { -// input, err := os.Open(path + filename[i]) -// if err != nil { -// log.Println("Ошибка открытия input файла: ", err) -// } -// defer func() { -// err = input.Close() -// if err != nil { -// log.Println("Ошибка закрытия input файла.") -// } -// }() -// -// output, err := os.Create(time.Now().Format("15-04-05") + "video.mkv") -// if err != nil { -// log.Println("Ошибка создания output файла: ", err) -// } -// defer func() { -// err = output.Close() -// if err != nil { -// log.Println("Ошибка закрытия output файла.") -// } -// -// }() -// -// _, err = io.Copy(output, input) -// if err != nil { -// log.Println("Ошибка копирования файла") -// } -// } -// -// log.Println("Файлы скопированы.") -// return filename, err -//} -// -//// MergeMKV принимает названия видеофайлов для объединения. -//func MergeMKV(filenames ...string) { -// // Создание файла со списком видеофайлов для объединения -// f, err := os.Create("videoList.txt") -// if err != nil { -// log.Fatalln("Ошибка создания файла со списком видеофайлов для объединения", err) -// } -// defer func() { -// err = f.Close() -// if err != nil { -// log.Fatalln("Ошибка закрытия файла: ", err.Error()) -// } -// }() -// -// // Запись видеофайлов для объединения -// n := len(filenames) -// -// for i := 0; i < n; i++ { -// _, err = f.WriteString("file '" + filenames[i] + "'\n") -// if err != nil { -// log.Fatalln(err) -// } -// } -// -// err = mergeFfmpeg(f) -// if err != nil { -// log.Fatalln("Ошибка объединения видеофайлов с помощью ffmpeg: ", err) -// } -// -// err = os.Remove("videoList.txt") -// if err != nil { -// log.Println("Ошибка удаления временного файла videoList.txt: ", err) -// } -// log.Println("Временный файл videoList.txt успешно удален") -//} -// -//// DeleteTrimmedFragments удаляет временно созданные файлы. -//func DeleteTrimmedFragments(filenames ...string) { -// n := len(filenames) -// -// for i := 0; i < n; i++ { -// err := os.Remove(filenames[i]) -// if err != nil { -// log.Printf("Ошибка удаления временного файла %s: %s", filenames[i], err.Error()) -// } -// log.Printf("Временный файл %s успешно удален", filenames[i]) -// } -//} -// -// -//// createFilename forms first part of the filename which contains time and date. -//func createFilename(time, date string) (fileName string) { -// s := []byte(time) -// s[3], s[4] = '0', '0' -// fileName = string(s) + "_" + date -// return fileName -//} -// -//// Добавление одного часа к строке в формате ЧЧ-ММ -//func addHour(startTime string) (fileName string) { -// s := []byte(startTime) -// if s[0] == '0' && s[1] == '9' { -// s[0] = '1' -// s[1] = '0' -// } else if s[0] == '1' && s[1] == '9' { -// s[0] = '2' -// s[1] = '0' -// } else { -// s[1]++ -// } -// fileName = string(s) -// return fileName -//} -// -//// Проверка наличия файла -//func checkFile(path, fileName string) (string, error) { -// found := false -// filenameIn := fileName -// -// dirEntry, err := os.ReadDir(path) -// if err != nil { -// return "", err -// } -// for _, entry := range dirEntry { -// if strings.Contains(entry.Name(), filenameIn) { -// filenameIn = entry.Name() -// break -// } -// return "", fmt.Errorf("file %s not found", filenameIn) -// } -// -// err = filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error { -// if err != nil { -// return err // Возвращаем ошибку, если возникла -// } -// if !info.IsDir() && info.Name() == filenameIn { -// log.Println("Файл обнаружен:", filePath) -// found = true -// return filepath.SkipDir // Останавливаем обход после нахождения -// } -// return nil -// }) -// -// if err != nil { -// return "", err -// } -// -// if !found { -// return "", errors.New("file not found") -// } -// -// return filenameIn, nil -//} diff --git a/reader/internal/processor/proc.go b/reader/internal/processor/proc.go index 7bc99dc..dfe28b5 100644 --- a/reader/internal/processor/proc.go +++ b/reader/internal/processor/proc.go @@ -2,6 +2,8 @@ package processor import ( "fmt" + + "git.insit.tech/psa/rtsp_reader-writer/writer/pkg/storage" ) func Process(date, startTime, endTime string) (string, error) { @@ -9,13 +11,13 @@ func Process(date, startTime, endTime string) (string, error) { path := "/home/psa/GoRepository/data/1280x720/" // Collect filenames into TXT file. - fileNamesTXT, err := createTXT(path, date, startTime, endTime) + fileNamesTXT, err := storage.createTXT(path, date, startTime, endTime) if err != nil { return "", fmt.Errorf("collect filenames into TXT file error: %s", err.Error()) } // Merge the collected files into one MP4 file. - fileNameRes, err := mergeFiles(path, fileNamesTXT) + fileNameRes, err := storage.mergeFiles(path, fileNamesTXT) if err != nil { return "", fmt.Errorf("merge files error: %s", err.Error()) } diff --git a/reader/internal/processor/time.go b/reader/internal/processor/time.go deleted file mode 100644 index a486594..0000000 --- a/reader/internal/processor/time.go +++ /dev/null @@ -1,47 +0,0 @@ -package processor - -import "log" - -// calcNeededTime accepts the start and the end of the recording time, converts the time from the format STRING to the -// format INT and returns the hour and the minute of the start recording time, the hour and the minute of the end -// recording time. -func calcNeededTime(startTime, endTime string) (startHour, startMinute, endHour, endMinute int) { - // Calc needed time. - startHour, startMinute, err := partitionTime(startTime) - if err != nil { - log.Fatal("Ошибка конвертации: ", err) - } - endHour, endMinute, err = partitionTime(endTime) - if err != nil { - log.Fatal("Ошибка конвертации: ", err) - } - - return startHour, startMinute, endHour, endMinute -} - -///////////////////////////////////////////////////////////////////////////////////// - -//// CalcEndMinuteFirstVideo calculates the need to change the hour (switching one fragment of video recording (which -//// lasts 1 hour) to another fragment of video recording) and returns the number of hours (required number of video -//// fragments), the duration of minutes (the object responsible for the indicator of minutes) of each fragment for the -//// formation of the final video (except for the last video fragment, provided DurationHour > 0 (the third object -//// returned by the CalcEndMinuteFirstVideo function)). -//// -//// In the case that you need to take a full fragment of the video file, for example, from 00-00 to 03-00, the -//// DurationHour value is conciliated by 1 and returned. -//func CalcEndMinuteFirstVideo(durationHour, endMinuteFirstVideo, startHour, endHour, endMinute int) ( -// durationHourCalc int, endMinuteFirstVideoCalc int) { -// durationHour = endHour - startHour -// -// if durationHour > 0 { -// endMinuteFirstVideo = 60 -// } else { -// endMinuteFirstVideo = endMinute -// } -// -// if endMinute == 0 && durationHour > 0 { -// durationHour -= 1 -// } -// -// return durationHour, endMinuteFirstVideo -//} diff --git a/reader/internal/unpacker/proc.go b/reader/internal/unpacker/proc.go index 7cffe79..09c6245 100644 --- a/reader/internal/unpacker/proc.go +++ b/reader/internal/unpacker/proc.go @@ -40,19 +40,13 @@ func CreateVideo() error { cam := log2.CamLogging( fmt.Sprintf("%s/%s/log/reader-cam_%s.log", config.DirData, file.Name(), strconv.FormatInt(time.Now().Unix(), 10))) - res, err := storage.ReadDir(fmt.Sprintf("%s/%s", config.DirData, file.Name())) + resolutions, err := storage.GetResolutions(file.Name()) if err != nil { cam.Error( "error reading directory", zap.String("dir", fmt.Sprintf("%s/%s", config.DirData, file.Name())), zap.Error(err)) return err } - resolutions := make([]string, 0) - for _, r := range res { - if r.IsDir() && r.Name() != "log" { - resolutions = append(resolutions, r.Name()) - } - } logger.Log.Info("start process camera:", zap.String("cam_name", file.Name())) log.Println("start process camera: ", file.Name()) @@ -125,7 +119,7 @@ func CreateVideo() error { return } headerReader := bytes.NewReader(segData) - headerSeg, err := readHeaderSegment(headerReader) + headerSeg, err := storage.ReadHeaderSegment(headerReader) if err != nil { cam.Error( "func readHeaderSegment error:", zap.String("filename", filenames[i]), zap.Error(err)) @@ -170,7 +164,7 @@ func CreateVideo() error { return } packetReader := bytes.NewReader(segData) - packets, err := readPacketSegment(packetReader) + packets, err := storage.ReadPacketSegment(packetReader) if err != nil { cam.Error( "func readPacketSegment error:", zap.String("filename", filenames[i]), zap.Error(err)) diff --git a/reader/internal/unpacker/unpack.go b/reader/internal/unpacker/unpack.go deleted file mode 100644 index 77feef7..0000000 --- a/reader/internal/unpacker/unpack.go +++ /dev/null @@ -1,109 +0,0 @@ -package unpacker - -import ( - "encoding/binary" - "fmt" - "git.insit.tech/psa/rtsp_reader-writer/writer/pkg/storage" - "io" -) - -// readString reads string length and then reads string data. -func readString(r io.Reader) (string, error) { - var length int32 - if err := binary.Read(r, binary.LittleEndian, &length); err != nil { - return "", err - } - buf := make([]byte, length) - if _, err := io.ReadFull(r, buf); err != nil { - return "", err - } - return string(buf), nil -} - -// readHeaderSegment reads header of the segment. -func readHeaderSegment(r io.Reader) (storage.Segment, error) { - var seg storage.Segment - date, err := readString(r) - if err != nil { - return seg, err - } - seg.Date = date - - duration, err := readString(r) - if err != nil { - return seg, err - } - seg.Duration = duration - - return seg, nil -} - -// readPacket reads one interleaved packet. -func readPacket(r io.Reader) (storage.InterleavedPacket, error) { - var pkt storage.InterleavedPacket - - // Read type of the packet. - typeByte := make([]byte, 1) - if _, err := io.ReadFull(r, typeByte); err != nil { - return pkt, err - } - pkt.Type = typeByte[0] - - // Read PTS (int64). - if err := binary.Read(r, binary.LittleEndian, &pkt.Pts); err != nil { - return pkt, err - } - - // Read data of the segment. - if pkt.Type == storage.PacketTypeH264 { - var numAUs int32 - if err := binary.Read(r, binary.LittleEndian, &numAUs); err != nil { - return pkt, err - } - var auList [][]byte - for i := 0; i < int(numAUs); i++ { - var auLen int32 - if err := binary.Read(r, binary.LittleEndian, &auLen); err != nil { - return pkt, err - } - auData := make([]byte, auLen) - if _, err := io.ReadFull(r, auData); err != nil { - return pkt, err - } - auList = append(auList, auData) - } - pkt.H264AUs = auList - } else if pkt.Type == storage.PacketTypeLPCM { - var auLen int32 - if err := binary.Read(r, binary.LittleEndian, &auLen); err != nil { - return pkt, err - } - auData := make([]byte, auLen) - if _, err := io.ReadFull(r, auData); err != nil { - return pkt, err - } - pkt.LPCMSamples = auData - } else { - return pkt, fmt.Errorf("unknown type of the packet: %d", pkt.Type) - } - - return pkt, nil -} - -// readPacketSegment reads segment packets. -func readPacketSegment(r io.Reader) ([]storage.InterleavedPacket, error) { - var numPackets int32 - if err := binary.Read(r, binary.LittleEndian, &numPackets); err != nil { - return nil, err - } - - var packets []storage.InterleavedPacket - for i := 0; i < int(numPackets); i++ { - pkt, err := readPacket(r) - if err != nil { - return nil, err - } - packets = append(packets, pkt) - } - return packets, nil -} diff --git a/writer/pkg/storage/file.go b/writer/pkg/storage/file.go index cd1699f..22dd68a 100644 --- a/writer/pkg/storage/file.go +++ b/writer/pkg/storage/file.go @@ -3,8 +3,17 @@ package storage import ( "bytes" "encoding/binary" + "errors" "fmt" "io" + "log" + "os" + "os/exec" + "strconv" + "strings" + "time" + + "git.insit.tech/psa/rtsp_reader-writer/writer/internal/config" ) // Constants for detection video and audio types. @@ -124,3 +133,448 @@ func WriteInterleavedPacket(w io.Writer, seg Segment) error { _, err := w.Write(segData) return err } + +// partitionTime convert numbers from type STRING to type INT. +func partitionTime(time string) (startHour int, startMinute int, err error) { + // Part hours and minutes. + s := []byte(time) + h := []byte{s[0], s[1]} + m := []byte{s[3], s[4]} + + // Transforms hours and minutes into INTs. + startHour, err = strconv.Atoi(string(h)) + if err != nil { + return 0, 0, err + } + startMinute, err = strconv.Atoi(string(m)) + if err != nil { + return 0, 0, err + } + + return startHour, startMinute, nil +} + +// CreateTXT collects filenames into TXT file. +func CreateTXT(path, date, startTime, endTime string) (string, error) { + //fileNames := make([]string, 0) + + fileNameTXT := startTime + "_" + endTime + ".txt" + f, err := os.Create(path + fileNameTXT) + if err != nil { + return "", fmt.Errorf("create file error: %s", err.Error()) + } + defer f.Close() + + // Read the directory. + dirEntry, err := os.ReadDir(path) + if err != nil { + return "", fmt.Errorf("read directory error: %s", err.Error()) + } + + // Calculate start time and end time of the required fragment. + startHour, startMinute, endHour, endMinute := calcNeededTime(startTime, endTime) + + for i := startHour; i <= endHour; i++ { + for j := startMinute; j < endMinute; j++ { // exclude the last minute as endMinute is the final time + for _, entry := range dirEntry { + if strings.Contains(entry.Name(), strconv.Itoa(i)+"-"+strconv.Itoa(j)+"-00"+"_"+date) { + _, err = f.WriteString("file '" + entry.Name() + "'\n") + if err != nil { + return "", fmt.Errorf("write file error: %s", err.Error()) + } + } + } + } + } + + return fileNameTXT, nil +} + +// mergeFiles merges the collected files into one MP4 file. +func mergeFiles(path, fileNamesTXT string) (string, error) { + fileNameRes := time.Now().Format("15-04-05") + "_video.mp4" + + cmd := exec.Command("ffmpeg", + "-f", "concat", + "-safe", "0", + "-fflags", "+genpts", + "-i", path+fileNamesTXT, + "-c", "copy", + path+fileNameRes) + + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("merge files error: %s", err.Error()) + } + + return fileNameRes, nil +} + +///////////////////////////////////////////////////////////////////////////////////// + +//// Функция copyFile находит файл в репозитории и создает его копию. +//func copyFiles(path string, filename []string) (filenameCopied []string, err error) { +// for i := 0; i < len(filename); i++ { +// input, err := os.Open(path + filename[i]) +// if err != nil { +// log.Println("Ошибка открытия input файла: ", err) +// } +// defer func() { +// err = input.Close() +// if err != nil { +// log.Println("Ошибка закрытия input файла.") +// } +// }() +// +// output, err := os.Create(time.Now().Format("15-04-05") + "video.mkv") +// if err != nil { +// log.Println("Ошибка создания output файла: ", err) +// } +// defer func() { +// err = output.Close() +// if err != nil { +// log.Println("Ошибка закрытия output файла.") +// } +// +// }() +// +// _, err = io.Copy(output, input) +// if err != nil { +// log.Println("Ошибка копирования файла") +// } +// } +// +// log.Println("Файлы скопированы.") +// return filename, err +//} +// +//// MergeMKV принимает названия видеофайлов для объединения. +//func MergeMKV(filenames ...string) { +// // Создание файла со списком видеофайлов для объединения +// f, err := os.Create("videoList.txt") +// if err != nil { +// log.Fatalln("Ошибка создания файла со списком видеофайлов для объединения", err) +// } +// defer func() { +// err = f.Close() +// if err != nil { +// log.Fatalln("Ошибка закрытия файла: ", err.Error()) +// } +// }() +// +// // Запись видеофайлов для объединения +// n := len(filenames) +// +// for i := 0; i < n; i++ { +// _, err = f.WriteString("file '" + filenames[i] + "'\n") +// if err != nil { +// log.Fatalln(err) +// } +// } +// +// err = mergeFfmpeg(f) +// if err != nil { +// log.Fatalln("Ошибка объединения видеофайлов с помощью ffmpeg: ", err) +// } +// +// err = os.Remove("videoList.txt") +// if err != nil { +// log.Println("Ошибка удаления временного файла videoList.txt: ", err) +// } +// log.Println("Временный файл videoList.txt успешно удален") +//} +// +//// DeleteTrimmedFragments удаляет временно созданные файлы. +//func DeleteTrimmedFragments(filenames ...string) { +// n := len(filenames) +// +// for i := 0; i < n; i++ { +// err := os.Remove(filenames[i]) +// if err != nil { +// log.Printf("Ошибка удаления временного файла %s: %s", filenames[i], err.Error()) +// } +// log.Printf("Временный файл %s успешно удален", filenames[i]) +// } +//} +// +// +//// createFilename forms first part of the filename which contains time and date. +//func createFilename(time, date string) (fileName string) { +// s := []byte(time) +// s[3], s[4] = '0', '0' +// fileName = string(s) + "_" + date +// return fileName +//} +// +//// Добавление одного часа к строке в формате ЧЧ-ММ +//func addHour(startTime string) (fileName string) { +// s := []byte(startTime) +// if s[0] == '0' && s[1] == '9' { +// s[0] = '1' +// s[1] = '0' +// } else if s[0] == '1' && s[1] == '9' { +// s[0] = '2' +// s[1] = '0' +// } else { +// s[1]++ +// } +// fileName = string(s) +// return fileName +//} +// +//// Проверка наличия файла +//func checkFile(path, fileName string) (string, error) { +// found := false +// filenameIn := fileName +// +// dirEntry, err := os.ReadDir(path) +// if err != nil { +// return "", err +// } +// for _, entry := range dirEntry { +// if strings.Contains(entry.Name(), filenameIn) { +// filenameIn = entry.Name() +// break +// } +// return "", fmt.Errorf("file %s not found", filenameIn) +// } +// +// err = filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error { +// if err != nil { +// return err // Возвращаем ошибку, если возникла +// } +// if !info.IsDir() && info.Name() == filenameIn { +// log.Println("Файл обнаружен:", filePath) +// found = true +// return filepath.SkipDir // Останавливаем обход после нахождения +// } +// return nil +// }) +// +// if err != nil { +// return "", err +// } +// +// if !found { +// return "", errors.New("file not found") +// } +// +// return filenameIn, nil +//} + +// GetResolutions parses all resolutions of a camera. +func GetResolutions(file string) ([]string, error) { + res, err := ReadDir(fmt.Sprintf("%s/%s", config.DirData, file)) + if err != nil { + return nil, err + } + + resolutions := make([]string, 0) + for _, r := range res { + if r.IsDir() && r.Name() != "log" { + resolutions = append(resolutions, r.Name()) + } + } + + return resolutions, nil +} + +// UnixToTime transfers Unix time to the formatted time. +func UnixToTime(unixTimeInt int) (string, error) { + t := time.Unix(int64(unixTimeInt), 0) + + return t.Format("15-04-05_02-01-2006"), nil +} + +// FileBytes returns file size in bytes. +func FileBytes(id, res, file string) (int64, error) { + filePath := fmt.Sprintf("%s/%s/%s/%s", config.DirData, id, res, file) + + info, err := os.Stat(filePath) + if err != nil { + return 0, err + } + + size := info.Size() + return size, nil +} + +// GetHeader +func GetHeader(id, res, file string) ([]string, error) { + // Open file for reading. + f, err := os.Open(fmt.Sprintf("%s/%s/%s/%s", config.DirData, id, res, file)) + if err != nil { + return nil, errors.New("opening file error for file: " + err.Error()) + } + defer f.Close() + + // Read StreamID. + var streamIDLen int32 + if err := binary.Read(f, binary.LittleEndian, &streamIDLen); err != nil { + return nil, errors.New("reading StreamID length error: " + err.Error()) + } + streamIDBytes := make([]byte, streamIDLen) + if _, err := io.ReadFull(f, streamIDBytes); err != nil { + return nil, errors.New("reading StreamID error: " + err.Error()) + } + + // Read header of the file. + var segLen int32 + if err := binary.Read(f, binary.LittleEndian, &segLen); err != nil { + return nil, errors.New("reading header length error: " + err.Error()) + } + segData := make([]byte, segLen) + if _, err := io.ReadFull(f, segData); err != nil { + return nil, errors.New("reading header error: " + err.Error()) + } + headerReader := bytes.NewReader(segData) + headerSeg, err := ReadHeaderSegment(headerReader) + if err != nil { + return nil, errors.New("reading header error: " + err.Error()) + } +} + +// readString reads string length and then reads string data. +func readString(r io.Reader) (string, error) { + var length int32 + if err := binary.Read(r, binary.LittleEndian, &length); err != nil { + return "", err + } + buf := make([]byte, length) + if _, err := io.ReadFull(r, buf); err != nil { + return "", err + } + return string(buf), nil +} + +// ReadHeaderSegment reads header of the segment. +func ReadHeaderSegment(r io.Reader) (Segment, error) { + var seg Segment + date, err := readString(r) + if err != nil { + return seg, err + } + seg.Date = date + + duration, err := readString(r) + if err != nil { + return seg, err + } + seg.Duration = duration + + return seg, nil +} + +// readPacket reads one interleaved packet. +func readPacket(r io.Reader) (InterleavedPacket, error) { + var pkt InterleavedPacket + + // Read type of the packet. + typeByte := make([]byte, 1) + if _, err := io.ReadFull(r, typeByte); err != nil { + return pkt, err + } + pkt.Type = typeByte[0] + + // Read PTS (int64). + if err := binary.Read(r, binary.LittleEndian, &pkt.Pts); err != nil { + return pkt, err + } + + // Read data of the segment. + if pkt.Type == PacketTypeH264 { + var numAUs int32 + if err := binary.Read(r, binary.LittleEndian, &numAUs); err != nil { + return pkt, err + } + var auList [][]byte + for i := 0; i < int(numAUs); i++ { + var auLen int32 + if err := binary.Read(r, binary.LittleEndian, &auLen); err != nil { + return pkt, err + } + auData := make([]byte, auLen) + if _, err := io.ReadFull(r, auData); err != nil { + return pkt, err + } + auList = append(auList, auData) + } + pkt.H264AUs = auList + } else if pkt.Type == PacketTypeLPCM { + var auLen int32 + if err := binary.Read(r, binary.LittleEndian, &auLen); err != nil { + return pkt, err + } + auData := make([]byte, auLen) + if _, err := io.ReadFull(r, auData); err != nil { + return pkt, err + } + pkt.LPCMSamples = auData + } else { + return pkt, fmt.Errorf("unknown type of the packet: %d", pkt.Type) + } + + return pkt, nil +} + +// readPacketSegment reads segment packets. +func readPacketSegment(r io.Reader) ([]InterleavedPacket, error) { + var numPackets int32 + if err := binary.Read(r, binary.LittleEndian, &numPackets); err != nil { + return nil, err + } + + var packets []InterleavedPacket + for i := 0; i < int(numPackets); i++ { + pkt, err := readPacket(r) + if err != nil { + return nil, err + } + packets = append(packets, pkt) + } + return packets, nil +} + +// calcNeededTime accepts the start and the end of the recording time, converts the time from the format STRING to the +// format INT and returns the hour and the minute of the start recording time, the hour and the minute of the end +// recording time. +func calcNeededTime(startTime, endTime string) (startHour, startMinute, endHour, endMinute int) { + // Calc needed time. + startHour, startMinute, err := partitionTime(startTime) + if err != nil { + log.Fatal("Ошибка конвертации: ", err) + } + endHour, endMinute, err = partitionTime(endTime) + if err != nil { + log.Fatal("Ошибка конвертации: ", err) + } + + return startHour, startMinute, endHour, endMinute +} + +///////////////////////////////////////////////////////////////////////////////////// + +//// CalcEndMinuteFirstVideo calculates the need to change the hour (switching one fragment of video recording (which +//// lasts 1 hour) to another fragment of video recording) and returns the number of hours (required number of video +//// fragments), the duration of minutes (the object responsible for the indicator of minutes) of each fragment for the +//// formation of the final video (except for the last video fragment, provided DurationHour > 0 (the third object +//// returned by the CalcEndMinuteFirstVideo function)). +//// +//// In the case that you need to take a full fragment of the video file, for example, from 00-00 to 03-00, the +//// DurationHour value is conciliated by 1 and returned. +//func CalcEndMinuteFirstVideo(durationHour, endMinuteFirstVideo, startHour, endHour, endMinute int) ( +// durationHourCalc int, endMinuteFirstVideoCalc int) { +// durationHour = endHour - startHour +// +// if durationHour > 0 { +// endMinuteFirstVideo = 60 +// } else { +// endMinuteFirstVideo = endMinute +// } +// +// if endMinute == 0 && durationHour > 0 { +// durationHour -= 1 +// } +// +// return durationHour, endMinuteFirstVideo +//}