init
This commit is contained in:
commit
fa47df6d89
22 changed files with 1724 additions and 0 deletions
298
import.go
Normal file
298
import.go
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"stream-poc2/ffmpeg"
|
||||
"stream-poc2/model"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func isFile(path string) bool {
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return !stat.IsDir()
|
||||
}
|
||||
|
||||
func isDir(path string) bool {
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return stat.IsDir()
|
||||
}
|
||||
|
||||
func createRawStructure(root string, audioStreams []int) error {
|
||||
dirs := []string{
|
||||
"",
|
||||
"video",
|
||||
"audio",
|
||||
"cache",
|
||||
"cache/video",
|
||||
}
|
||||
|
||||
for _, dir := range dirs {
|
||||
log.Println("mkdir:", dir)
|
||||
err := os.MkdirAll(path.Join(root, dir), 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, streamIdx := range audioStreams {
|
||||
dir := path.Join(root, "audio", strconv.Itoa(streamIdx))
|
||||
log.Println("mkdir:", dir)
|
||||
err := os.MkdirAll(dir, 0o755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeJSON(fpath string, data any) error {
|
||||
log.Println("writing", fpath)
|
||||
f, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func(f *os.File) {
|
||||
err := f.Close()
|
||||
if err != nil {
|
||||
log.Println("Failed to close", fpath, err)
|
||||
}
|
||||
}(f)
|
||||
|
||||
err = json.NewEncoder(f).Encode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func importMain(filepath, target string) {
|
||||
targetFullPath := path.Join("db", target)
|
||||
|
||||
if isDir(targetFullPath) {
|
||||
fmt.Println("target already exists")
|
||||
return
|
||||
}
|
||||
|
||||
if !isFile(filepath) {
|
||||
fmt.Println("Source does not exists")
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Running FFProbe...")
|
||||
|
||||
ffProbed, err := ffmpeg.FFProbe(filepath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var numVideoStreams int
|
||||
|
||||
// we will fill those out from ffprobe's output
|
||||
nativeVideoStream := model.VideoStream{}
|
||||
var audioStreams []model.AudioStream
|
||||
|
||||
for _, stream := range ffProbed.Streams {
|
||||
log.Println("Discovered stream:", stream.Index, stream.CodecType)
|
||||
if stream.CodecType == "audio" {
|
||||
lang, ok := stream.Tags["language"]
|
||||
if !ok {
|
||||
lang = "eng" // use as default
|
||||
}
|
||||
title, _ := stream.Tags["title"]
|
||||
if !ok {
|
||||
title = fmt.Sprintf("Unknown (%d)", stream.Index) // Title is used as name which must be different
|
||||
}
|
||||
|
||||
sampleRate, err := strconv.Atoi(stream.SampleRate)
|
||||
if err != nil {
|
||||
log.Println("!!! Couldn't get audio sample rate !!!")
|
||||
}
|
||||
|
||||
bitRate, err := strconv.Atoi(stream.BitRate)
|
||||
if err != nil {
|
||||
log.Println("!!! Couldn't get audio bit rate !!!")
|
||||
}
|
||||
|
||||
audioStreams = append(audioStreams, model.AudioStream{
|
||||
Index: stream.Index,
|
||||
Lang: lang,
|
||||
Title: title,
|
||||
Default: stream.Disposition.Default == 1,
|
||||
Codec: stream.CodecName,
|
||||
SampleRate: sampleRate,
|
||||
Bitrate: bitRate,
|
||||
Channels: stream.Channels,
|
||||
AudioChunks: nil,
|
||||
})
|
||||
}
|
||||
if stream.CodecType == "video" {
|
||||
// funnily mkv can encode any sort of files, including "cover images", those represented as video frames.
|
||||
frameRateParts := strings.Split(stream.AvgFrameRate, "/")
|
||||
a, err := strconv.Atoi(frameRateParts[0])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b, err := strconv.Atoi(frameRateParts[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if a > 0 && b > 0 {
|
||||
nativeVideoStream.FrameRate = float64(a) / float64(b)
|
||||
}
|
||||
|
||||
if nativeVideoStream.FrameRate == 0 || strings.HasPrefix(stream.Tags["mimetype"], "image") {
|
||||
log.Println("This is a still image, ignoring...")
|
||||
continue
|
||||
}
|
||||
|
||||
numVideoStreams++
|
||||
nativeVideoStream.Resolution = model.Resolution{
|
||||
Width: stream.Width,
|
||||
Height: stream.Height,
|
||||
}
|
||||
if stream.BitRate != "" {
|
||||
nativeVideoStream.PeakBitrate, err = strconv.Atoi(stream.BitRate)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
nativeVideoStream.AvgBitrate, err = strconv.Atoi(stream.BitRate)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
log.Println("Warning: couldn't extract video bitrate")
|
||||
}
|
||||
|
||||
nativeVideoStream.HLSCodecsString, _ = ffmpeg.FigureOutHLSCodecsParam(stream) // ignore if it's unrecognizable..
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if numVideoStreams != 1 {
|
||||
log.Printf("!!! The input media should have exactly one video stream, this has %d !!!\n", numVideoStreams)
|
||||
}
|
||||
|
||||
metadata := model.MediaMetadata{
|
||||
FriendlyName: path.Base(filepath),
|
||||
TargetDuration: ffmpeg.SlicerTargetDurationSec,
|
||||
VideoStream: nativeVideoStream,
|
||||
AudioStreams: audioStreams,
|
||||
}
|
||||
|
||||
audioStreamIndexes := make([]int, len(audioStreams))
|
||||
for i, audioStream := range audioStreams {
|
||||
audioStreamIndexes[i] = audioStream.Index
|
||||
}
|
||||
|
||||
// create structure
|
||||
err = createRawStructure(targetFullPath, audioStreamIndexes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// write ffprobeOutput
|
||||
err = writeJSON(path.Join(targetFullPath, "ffprobe.json"), ffProbed)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Println("Now segmenting...")
|
||||
segmentsVideoCSV, audioSegmentsCSVs, err := ffmpeg.MakeSegments(filepath, targetFullPath, audioStreams)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Println("Segment data: ", segmentsVideoCSV, audioSegmentsCSVs)
|
||||
|
||||
log.Println("Now processing segment data...")
|
||||
|
||||
// read slice stuff
|
||||
segments, err := ffmpeg.ReadSegmentsCSV(segmentsVideoCSV)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// load videoStream chunks
|
||||
videoChunks := make([]model.Chunk, len(segments))
|
||||
for i, segment := range segments {
|
||||
videoChunks[i] = model.Chunk{
|
||||
FName: segment.Name,
|
||||
Length: segment.Len(),
|
||||
}
|
||||
}
|
||||
|
||||
metadata.VideoStream.VideoChunks = videoChunks
|
||||
|
||||
// load audioStream chunks
|
||||
for i, slicesFile := range audioSegmentsCSVs {
|
||||
segments, err = ffmpeg.ReadSegmentsCSV(slicesFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
audioChunks := make([]model.Chunk, len(segments))
|
||||
for j, segment := range segments {
|
||||
audioChunks[j] = model.Chunk{
|
||||
FName: segment.Name,
|
||||
Length: segment.Len(),
|
||||
}
|
||||
}
|
||||
metadata.AudioStreams[i].AudioChunks = audioChunks
|
||||
}
|
||||
|
||||
log.Println("Writing metadata...")
|
||||
|
||||
// write metadata
|
||||
err = writeJSON(path.Join(targetFullPath, "meta.json"), metadata)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// pre-encode
|
||||
log.Println("Pre-encoding some slices...")
|
||||
|
||||
for i, chunk := range videoChunks {
|
||||
// encode 7 slices to begin with...
|
||||
if i > 6 {
|
||||
break
|
||||
}
|
||||
|
||||
origSegmentPath := path.Join(targetFullPath, "video", chunk.FName)
|
||||
for profileName, profileSettings := range model.VideoProfiles {
|
||||
preEncodedSegmentDir := path.Join(targetFullPath, "cache", "video", profileName)
|
||||
err = os.MkdirAll(preEncodedSegmentDir, 0o755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
preEncodedSegmentPath := path.Join(preEncodedSegmentDir, chunk.FName)
|
||||
log.Println(origSegmentPath, " -> ", preEncodedSegmentPath)
|
||||
|
||||
var f *os.File
|
||||
f, err = os.OpenFile(preEncodedSegmentPath, os.O_WRONLY|os.O_CREATE, 0o644)
|
||||
if err != nil {
|
||||
log.Println("failed to open chunk", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ffmpeg.EncodeChunk(profileSettings, nativeVideoStream.Resolution.Height, "", origSegmentPath, f)
|
||||
_ = f.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue