Refactoring.
This commit is contained in:
parent
7c084d3e9e
commit
60d315c6f6
@ -13,9 +13,13 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
//port := 8080
|
//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 /api/v1/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 /api/v1/hls/", handlers.HLS)
|
||||||
//http.HandleFunc("GET /vods", handlers.ListVodsHandler)
|
//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.Println("Starting server on:")
|
||||||
//log.Printf("Serving on HTTP port: %d\n", port)
|
//log.Printf("Serving on HTTP port: %d\n", port)
|
||||||
@ -33,6 +37,6 @@ func main() {
|
|||||||
|
|
||||||
err = unpacker.CreateVideo()
|
err = unpacker.CreateVideo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Error("Failed to create flow", zap.Error(err))
|
logger.Log.Error("failed to create flow", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
//}
|
|
@ -2,6 +2,8 @@ package processor
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.insit.tech/psa/rtsp_reader-writer/writer/pkg/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Process(date, startTime, endTime string) (string, error) {
|
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/"
|
path := "/home/psa/GoRepository/data/1280x720/"
|
||||||
|
|
||||||
// Collect filenames into TXT file.
|
// Collect filenames into TXT file.
|
||||||
fileNamesTXT, err := createTXT(path, date, startTime, endTime)
|
fileNamesTXT, err := storage.createTXT(path, date, startTime, endTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("collect filenames into TXT file error: %s", err.Error())
|
return "", fmt.Errorf("collect filenames into TXT file error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge the collected files into one MP4 file.
|
// Merge the collected files into one MP4 file.
|
||||||
fileNameRes, err := mergeFiles(path, fileNamesTXT)
|
fileNameRes, err := storage.mergeFiles(path, fileNamesTXT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("merge files error: %s", err.Error())
|
return "", fmt.Errorf("merge files error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
//}
|
|
@ -40,19 +40,13 @@ func CreateVideo() error {
|
|||||||
cam := log2.CamLogging(
|
cam := log2.CamLogging(
|
||||||
fmt.Sprintf("%s/%s/log/reader-cam_%s.log", config.DirData, file.Name(), strconv.FormatInt(time.Now().Unix(), 10)))
|
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 {
|
if err != nil {
|
||||||
cam.Error(
|
cam.Error(
|
||||||
"error reading directory",
|
"error reading directory",
|
||||||
zap.String("dir", fmt.Sprintf("%s/%s", config.DirData, file.Name())), zap.Error(err))
|
zap.String("dir", fmt.Sprintf("%s/%s", config.DirData, file.Name())), zap.Error(err))
|
||||||
return 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()))
|
logger.Log.Info("start process camera:", zap.String("cam_name", file.Name()))
|
||||||
log.Println("start process camera: ", file.Name())
|
log.Println("start process camera: ", file.Name())
|
||||||
@ -125,7 +119,7 @@ func CreateVideo() error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
headerReader := bytes.NewReader(segData)
|
headerReader := bytes.NewReader(segData)
|
||||||
headerSeg, err := readHeaderSegment(headerReader)
|
headerSeg, err := storage.ReadHeaderSegment(headerReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cam.Error(
|
cam.Error(
|
||||||
"func readHeaderSegment error:", zap.String("filename", filenames[i]), zap.Error(err))
|
"func readHeaderSegment error:", zap.String("filename", filenames[i]), zap.Error(err))
|
||||||
@ -170,7 +164,7 @@ func CreateVideo() error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
packetReader := bytes.NewReader(segData)
|
packetReader := bytes.NewReader(segData)
|
||||||
packets, err := readPacketSegment(packetReader)
|
packets, err := storage.ReadPacketSegment(packetReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cam.Error(
|
cam.Error(
|
||||||
"func readPacketSegment error:", zap.String("filename", filenames[i]), zap.Error(err))
|
"func readPacketSegment error:", zap.String("filename", filenames[i]), zap.Error(err))
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -3,8 +3,17 @@ package storage
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"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.
|
// Constants for detection video and audio types.
|
||||||
@ -124,3 +133,448 @@ func WriteInterleavedPacket(w io.Writer, seg Segment) error {
|
|||||||
_, err := w.Write(segData)
|
_, err := w.Write(segData)
|
||||||
return err
|
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
|
||||||
|
//}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user