From 38d6f752f5fc0fc5dd1de6e26498feb01be2b6d0 Mon Sep 17 00:00:00 2001 From: rexim Date: Mon, 7 Oct 2024 01:14:45 +0700 Subject: [PATCH] Inline all the subcommand functions into the subcommands array --- markut.go | 754 ++++++++++++++++++++++++++---------------------------- 1 file changed, 369 insertions(+), 385 deletions(-) diff --git a/markut.go b/markut.go index c9eee2e..9dd8d9b 100644 --- a/markut.go +++ b/markut.go @@ -894,468 +894,452 @@ func ffmpegGenerateConcatList(chunks []Chunk, outputPath string) error { return nil } -func finalSubcommand(args []string) bool { - subFlag := flag.NewFlagSet("final", flag.ContinueOnError) - markutPtr := subFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") - patchPtr := subFlag.Bool("patch", false, "Patch modified cuts") - - err := subFlag.Parse(args) - if err == flag.ErrHelp { - return true +func captionsRingPush(ring []ChatMessage, message ChatMessage, capacity int) []ChatMessage { + if len(ring) < capacity { + return append(ring, message) } + return append(ring[1:], message) +} - if err != nil { - fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); - return false - } +type Subcommand struct { + Name string + Run func(args []string) bool + Description string +} - context := defaultContext() - ok := context.evalMarkutFile(*markutPtr) && context.finishEval() - if !ok { - return false - } +var Subcommands = []Subcommand{ + { + Name: "fixup", + Description: "Fixup the initial footage", + Run: func(args []string) bool { + subFlag := flag.NewFlagSet("fixup", flag.ExitOnError) + inputPtr := subFlag.String("input", "", "Path to the input video file (mandatory)") + outputPtr := subFlag.String("output", "input.ts", "Path to the output video file") + yPtr := subFlag.Bool("y", false, "Pass -y to ffmpeg") + + err := subFlag.Parse(args) + if err == flag.ErrHelp { + return true + } - if *patchPtr { - for _, i := range context.modified_cuts { - chunk := context.chunks[i] - err := ffmpegCutChunk(context, chunk) if err != nil { - fmt.Printf("WARNING: Failed to cut chunk %s: %s\n", chunk, err) + fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); + return false } - if i+1 < len(context.chunks) { - chunk = context.chunks[i+1] - err = ffmpegCutChunk(context, chunk) - if err != nil { - fmt.Printf("WARNING: Failed to cut chunk %s: %s\n", chunk.Name(), err) - } + + if *inputPtr == "" { + subFlag.Usage() + fmt.Printf("ERROR: No -input file is provided\n") + return false } - } - } else { - for _, chunk := range context.chunks { - err := ffmpegCutChunk(context, chunk) + + err = ffmpegFixupInput(*inputPtr, *outputPtr, *yPtr) if err != nil { - fmt.Printf("WARNING: Failed to cut chunk %s: %s\n", chunk.Name(), err) + fmt.Printf("ERROR: Could not fixup input file %s: %s\n", *inputPtr, err) + return false } - } - } - - listPath := "final-list.txt" - err = ffmpegGenerateConcatList(context.chunks, listPath) - if err != nil { - fmt.Printf("ERROR: Could not generate final concat list %s: %s\n", listPath, err) - return false; - } - - err = ffmpegConcatChunks(listPath, context.outputPath) - if err != nil { - fmt.Printf("ERROR: Could not generated final output %s: %s\n", context.outputPath, err) - return false - } - - context.PrintSummary() - - return true -} + fmt.Printf("Generated %s\n", *outputPtr) -func cutSubcommand(args []string) bool { - subFlag := flag.NewFlagSet("cut", flag.ContinueOnError) - markutPtr := subFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") - - err := subFlag.Parse(args) - if err == flag.ErrHelp { - return true - } - - if err != nil { - fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); - return false - } - - context := defaultContext() - ok := context.evalMarkutFile(*markutPtr) && context.finishEval() - if !ok { - return false - } + return true + }, + }, + { + Name: "cut", + Description: "Render specific cut of the final video", + Run: func (args []string) bool { + subFlag := flag.NewFlagSet("cut", flag.ContinueOnError) + markutPtr := subFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") - if len(context.cuts) == 0 { - fmt.Printf("ERROR: No cuts are provided. Use `cut` command after a `chunk` command to define a cut\n"); - return false; - } + err := subFlag.Parse(args) + if err == flag.ErrHelp { + return true + } - for _, cut := range context.cuts { - if cut.chunk+1 >= len(context.chunks) { - fmt.Printf("ERROR: %d is an invalid cut number. There is only %d of them.\n", cut.chunk, len(context.chunks)-1) - return false - } + if err != nil { + fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); + return false + } - cutChunks := []Chunk{ - { - Start: context.chunks[cut.chunk].End - cut.pad, - End: context.chunks[cut.chunk].End, - InputPath: context.chunks[cut.chunk].InputPath, - }, - { - Start: context.chunks[cut.chunk+1].Start, - End: context.chunks[cut.chunk+1].Start + cut.pad, - InputPath: context.chunks[cut.chunk+1].InputPath, - }, - } + context := defaultContext() + ok := context.evalMarkutFile(*markutPtr) && context.finishEval() + if !ok { + return false + } - for _, chunk := range cutChunks { - err := ffmpegCutChunk(context, chunk) - if err != nil { - fmt.Printf("WARNING: Failed to cut chunk %s: %s\n", chunk.Name(), err) + if len(context.cuts) == 0 { + fmt.Printf("ERROR: No cuts are provided. Use `cut` command after a `chunk` command to define a cut\n"); + return false; } - } - cutListPath := "cut-%02d-list.txt" - listPath := fmt.Sprintf(cutListPath, cut.chunk) - err = ffmpegGenerateConcatList(cutChunks, listPath) - if err != nil { - fmt.Printf("ERROR: Could not generate not generate cut concat list %s: %s\n", cutListPath, err) - return false - } + for _, cut := range context.cuts { + if cut.chunk+1 >= len(context.chunks) { + fmt.Printf("ERROR: %d is an invalid cut number. There is only %d of them.\n", cut.chunk, len(context.chunks)-1) + return false + } - cutOutputPath := fmt.Sprintf("cut-%02d.mp4", cut.chunk) - err = ffmpegConcatChunks(listPath, cutOutputPath) - if err != nil { - fmt.Printf("ERROR: Could not generate cut output file %s: %s\n", cutOutputPath, err) - return false - } + cutChunks := []Chunk{ + { + Start: context.chunks[cut.chunk].End - cut.pad, + End: context.chunks[cut.chunk].End, + InputPath: context.chunks[cut.chunk].InputPath, + }, + { + Start: context.chunks[cut.chunk+1].Start, + End: context.chunks[cut.chunk+1].Start + cut.pad, + InputPath: context.chunks[cut.chunk+1].InputPath, + }, + } - fmt.Printf("Generated %s\n", cutOutputPath); - fmt.Printf("%s: NOTE: cut is defined in here\n", context.chunks[cut.chunk].Loc); - } + for _, chunk := range cutChunks { + err := ffmpegCutChunk(context, chunk) + if err != nil { + fmt.Printf("WARNING: Failed to cut chunk %s: %s\n", chunk.Name(), err) + } + } - return true -} + cutListPath := "cut-%02d-list.txt" + listPath := fmt.Sprintf(cutListPath, cut.chunk) + err = ffmpegGenerateConcatList(cutChunks, listPath) + if err != nil { + fmt.Printf("ERROR: Could not generate not generate cut concat list %s: %s\n", cutListPath, err) + return false + } -func summarySubcommand(args []string) bool { - summFlag := flag.NewFlagSet("summary", flag.ContinueOnError) - markutPtr := summFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") + cutOutputPath := fmt.Sprintf("cut-%02d.mp4", cut.chunk) + err = ffmpegConcatChunks(listPath, cutOutputPath) + if err != nil { + fmt.Printf("ERROR: Could not generate cut output file %s: %s\n", cutOutputPath, err) + return false + } - err := summFlag.Parse(args) + fmt.Printf("Generated %s\n", cutOutputPath); + fmt.Printf("%s: NOTE: cut is defined in here\n", context.chunks[cut.chunk].Loc); + } - if err == flag.ErrHelp { - return true - } + return true + }, + }, + { + Name: "chunk", + Description: "Render specific chunk of the final video", + Run: func (args []string) bool { + subFlag := flag.NewFlagSet("chunk", flag.ContinueOnError) + markutPtr := subFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") + chunkPtr := subFlag.Int("chunk", 0, "Chunk number to render") - if err != nil { - fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); - return false - } + err := subFlag.Parse(args) - context := defaultContext(); - ok := context.evalMarkutFile(*markutPtr) && context.finishEval() - if !ok { - return false - } + if err == flag.ErrHelp { + return true + } - context.PrintSummary() + if err != nil { + fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); + return false + } - return true -} + context := defaultContext(); + ok := context.evalMarkutFile(*markutPtr) && context.finishEval() + if !ok { + return false + } -func captionsRingPush(ring []ChatMessage, message ChatMessage, capacity int) []ChatMessage { - if len(ring) < capacity { - return append(ring, message) - } - return append(ring[1:], message) -} + if *chunkPtr > len(context.chunks) { + fmt.Printf("ERROR: %d is an incorrect chunk number. There is only %d of them.\n", *chunkPtr, len(context.chunks)) + return false + } -func chatSubcommand(args []string) bool { - chatFlag := flag.NewFlagSet("chat", flag.ContinueOnError) - markutPtr := chatFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") + chunk := context.chunks[*chunkPtr] - err := chatFlag.Parse(args) + err = ffmpegCutChunk(context, chunk) + if err != nil { + fmt.Printf("ERROR: Could not cut the chunk %s: %s\n", chunk.Name(), err) + return false + } - if err == flag.ErrHelp { - return true - } + fmt.Printf("%s is rendered!\n", chunk.Name()) + return true + }, + }, + { + Name: "final", + Description: "Render the final video", + Run: func (args []string) bool { + subFlag := flag.NewFlagSet("final", flag.ContinueOnError) + markutPtr := subFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") + patchPtr := subFlag.Bool("patch", false, "Patch modified cuts") + + err := subFlag.Parse(args) + if err == flag.ErrHelp { + return true + } - if err != nil { - fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); - return false - } + if err != nil { + fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); + return false + } - context := defaultContext() - ok := context.evalMarkutFile(*markutPtr) && context.finishEval() - if !ok { - return false - } + context := defaultContext() + ok := context.evalMarkutFile(*markutPtr) && context.finishEval() + if !ok { + return false + } - capacity := 1 - ring := []ChatMessage{} - timeCursor := Millis(0) - subRipCounter := 0; - for _, chunk := range context.chunks { - prevTime := chunk.Start - for _, message := range chunk.ChatLog { - deltaTime := message.TimeOffset - prevTime - prevTime = message.TimeOffset - if len(ring) > 0 { - subRipCounter += 1 - fmt.Printf("%d\n", subRipCounter); - fmt.Printf("%s --> %s\n", millisToSubRipTs(timeCursor), millisToSubRipTs(timeCursor + deltaTime)); - for _, ringMessage := range ring { - fmt.Printf("%s\n", ringMessage.Text); + if *patchPtr { + for _, i := range context.modified_cuts { + chunk := context.chunks[i] + err := ffmpegCutChunk(context, chunk) + if err != nil { + fmt.Printf("WARNING: Failed to cut chunk %s: %s\n", chunk, err) + } + if i+1 < len(context.chunks) { + chunk = context.chunks[i+1] + err = ffmpegCutChunk(context, chunk) + if err != nil { + fmt.Printf("WARNING: Failed to cut chunk %s: %s\n", chunk.Name(), err) + } + } + } + } else { + for _, chunk := range context.chunks { + err := ffmpegCutChunk(context, chunk) + if err != nil { + fmt.Printf("WARNING: Failed to cut chunk %s: %s\n", chunk.Name(), err) + } } - fmt.Printf("\n") } - timeCursor += deltaTime - ring = captionsRingPush(ring, message, capacity); - } - timeCursor += chunk.End - prevTime - } - return true -} - -func chunkSubcommand(args []string) bool { - subFlag := flag.NewFlagSet("chunk", flag.ContinueOnError) - markutPtr := subFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") - chunkPtr := subFlag.Int("chunk", 0, "Chunk number to render") + listPath := "final-list.txt" + err = ffmpegGenerateConcatList(context.chunks, listPath) + if err != nil { + fmt.Printf("ERROR: Could not generate final concat list %s: %s\n", listPath, err) + return false; + } - err := subFlag.Parse(args) + err = ffmpegConcatChunks(listPath, context.outputPath) + if err != nil { + fmt.Printf("ERROR: Could not generated final output %s: %s\n", context.outputPath, err) + return false + } - if err == flag.ErrHelp { - return true - } + context.PrintSummary() - if err != nil { - fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); - return false - } + return true + }, + }, + { + Name: "summary", + Description: "Print the summary of the video", + Run: func (args []string) bool { + summFlag := flag.NewFlagSet("summary", flag.ContinueOnError) + markutPtr := summFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") - context := defaultContext(); - ok := context.evalMarkutFile(*markutPtr) && context.finishEval() - if !ok { - return false - } + err := summFlag.Parse(args) - if *chunkPtr > len(context.chunks) { - fmt.Printf("ERROR: %d is an incorrect chunk number. There is only %d of them.\n", *chunkPtr, len(context.chunks)) - return false - } + if err == flag.ErrHelp { + return true + } - chunk := context.chunks[*chunkPtr] + if err != nil { + fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); + return false + } - err = ffmpegCutChunk(context, chunk) - if err != nil { - fmt.Printf("ERROR: Could not cut the chunk %s: %s\n", chunk.Name(), err) - return false - } + context := defaultContext(); + ok := context.evalMarkutFile(*markutPtr) && context.finishEval() + if !ok { + return false + } - fmt.Printf("%s is rendered!\n", chunk.Name()) - return true -} + context.PrintSummary() -func fixupSubcommand(args []string) bool { - subFlag := flag.NewFlagSet("fixup", flag.ExitOnError) - inputPtr := subFlag.String("input", "", "Path to the input video file (mandatory)") - outputPtr := subFlag.String("output", "input.ts", "Path to the output video file") - yPtr := subFlag.Bool("y", false, "Pass -y to ffmpeg") + return true + }, + }, + { + Name: "chat", + Description: "Generate chat captions", + Run: func (args []string) bool { + chatFlag := flag.NewFlagSet("chat", flag.ContinueOnError) + markutPtr := chatFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") - err := subFlag.Parse(args) - if err == flag.ErrHelp { - return true - } + err := chatFlag.Parse(args) - if err != nil { - fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); - return false - } + if err == flag.ErrHelp { + return true + } - if *inputPtr == "" { - subFlag.Usage() - fmt.Printf("ERROR: No -input file is provided\n") - return false - } + if err != nil { + fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); + return false + } - err = ffmpegFixupInput(*inputPtr, *outputPtr, *yPtr) - if err != nil { - fmt.Printf("ERROR: Could not fixup input file %s: %s\n", *inputPtr, err) - return false - } - fmt.Printf("Generated %s\n", *outputPtr) + context := defaultContext() + ok := context.evalMarkutFile(*markutPtr) && context.finishEval() + if !ok { + return false + } - return true -} + capacity := 1 + ring := []ChatMessage{} + timeCursor := Millis(0) + subRipCounter := 0; + for _, chunk := range context.chunks { + prevTime := chunk.Start + for _, message := range chunk.ChatLog { + deltaTime := message.TimeOffset - prevTime + prevTime = message.TimeOffset + if len(ring) > 0 { + subRipCounter += 1 + fmt.Printf("%d\n", subRipCounter); + fmt.Printf("%s --> %s\n", millisToSubRipTs(timeCursor), millisToSubRipTs(timeCursor + deltaTime)); + for _, ringMessage := range ring { + fmt.Printf("%s\n", ringMessage.Text); + } + fmt.Printf("\n") + } + timeCursor += deltaTime + ring = captionsRingPush(ring, message, capacity); + } + timeCursor += chunk.End - prevTime + } -func pruneSubcommand(args []string) bool { - subFlag := flag.NewFlagSet("prune", flag.ContinueOnError) - markutPtr := subFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") + return true + }, + }, + { + Name: "prune", + Description: "Prune unused chunks", + Run: func (args []string) bool { + subFlag := flag.NewFlagSet("prune", flag.ContinueOnError) + markutPtr := subFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") - err := subFlag.Parse(args) + err := subFlag.Parse(args) - if err == flag.ErrHelp { - return true - } + if err == flag.ErrHelp { + return true + } - if err != nil { - fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); - return false - } + if err != nil { + fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); + return false + } - context := defaultContext(); - ok := context.evalMarkutFile(*markutPtr) && context.finishEval() - if !ok { - return false - } + context := defaultContext(); + ok := context.evalMarkutFile(*markutPtr) && context.finishEval() + if !ok { + return false + } - files, err := ioutil.ReadDir(ChunksFolder) - if err != nil { - fmt.Printf("ERROR: could not read %s folder: %s\n", ChunksFolder, err); - return false; - } + files, err := ioutil.ReadDir(ChunksFolder) + if err != nil { + fmt.Printf("ERROR: could not read %s folder: %s\n", ChunksFolder, err); + return false; + } - for _, file := range files { - if !file.IsDir() { - filePath := fmt.Sprintf("%s/%s", ChunksFolder, file.Name()); - if !context.containsChunkWithName(filePath) { - fmt.Printf("INFO: deleting chunk file %s\n", filePath); - err = os.Remove(filePath) - if err != nil { - fmt.Printf("ERROR: could not remove file %s: %s\n", filePath, err) - return false; + for _, file := range files { + if !file.IsDir() { + filePath := fmt.Sprintf("%s/%s", ChunksFolder, file.Name()); + if !context.containsChunkWithName(filePath) { + fmt.Printf("INFO: deleting chunk file %s\n", filePath); + err = os.Remove(filePath) + if err != nil { + fmt.Printf("ERROR: could not remove file %s: %s\n", filePath, err) + return false; + } + } } } - } - } - fmt.Printf("DONE\n"); + fmt.Printf("DONE\n"); - return true -} - -// TODO: Maybe watch mode should just be a flag for the `final` subcommand -func watchSubcommand(args []string) bool { - subFlag := flag.NewFlagSet("watch", flag.ContinueOnError) - markutPtr := subFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") - skipcatPtr := subFlag.Bool("skipcat", false, "Skip concatenation step") - - err := subFlag.Parse(args) - - if err == flag.ErrHelp { - return true - } - - if err != nil { - fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); - return false - } + return true + }, + }, + // TODO: Maybe watch mode should just be a flag for the `final` subcommand + { + Name: "watch", + Description: "Render finished chunks in watch mode every time MARKUT file is modified", + Run: func (args []string) bool { + subFlag := flag.NewFlagSet("watch", flag.ContinueOnError) + markutPtr := subFlag.String("markut", "MARKUT", "Path to the Markut file with markers (mandatory)") + skipcatPtr := subFlag.Bool("skipcat", false, "Skip concatenation step") - fmt.Printf("INFO: Waiting for updates to %s\n", *markutPtr) - for { - // NOTE: always use rsync(1) for updating the MARKUT file remotely. - // This kind of crappy modification checking needs at least some sort of atomicity. - // rsync(1) is as atomic as rename(2). So it's alright for majority of the cases. + err := subFlag.Parse(args) - context := defaultContext(); - ok := context.evalMarkutFile(*markutPtr) && context.finishEval() - if !ok { - return false - } + if err == flag.ErrHelp { + return true + } - done := true - for _, chunk := range(context.chunks) { - if chunk.Unfinished { - done = false - continue + if err != nil { + fmt.Printf("ERROR: Could not parse command line arguments: %s\n", err); + return false } - if _, err := os.Stat(chunk.Name()); errors.Is(err, os.ErrNotExist) { - err = ffmpegCutChunk(context, chunk) - if err != nil { - fmt.Printf("ERROR: Could not cut the chunk %s: %s\n", chunk.Name(), err) + fmt.Printf("INFO: Waiting for updates to %s\n", *markutPtr) + for { + // NOTE: always use rsync(1) for updating the MARKUT file remotely. + // This kind of crappy modification checking needs at least some sort of atomicity. + // rsync(1) is as atomic as rename(2). So it's alright for majority of the cases. + + context := defaultContext(); + ok := context.evalMarkutFile(*markutPtr) && context.finishEval() + if !ok { return false } - fmt.Printf("INFO: Waiting for more updates to %s\n", *markutPtr) - done = false - break - } - } - if done { - break - } - - time.Sleep(1 * time.Second) - } + done := true + for _, chunk := range(context.chunks) { + if chunk.Unfinished { + done = false + continue + } + + if _, err := os.Stat(chunk.Name()); errors.Is(err, os.ErrNotExist) { + err = ffmpegCutChunk(context, chunk) + if err != nil { + fmt.Printf("ERROR: Could not cut the chunk %s: %s\n", chunk.Name(), err) + return false + } + fmt.Printf("INFO: Waiting for more updates to %s\n", *markutPtr) + done = false + break + } + } - context := defaultContext() - ok := context.evalMarkutFile(*markutPtr) && context.finishEval() - if !ok { - return false - } + if done { + break + } - if !*skipcatPtr { + time.Sleep(1 * time.Second) + } - listPath := "final-list.txt" - err = ffmpegGenerateConcatList(context.chunks, listPath) - if err != nil { - fmt.Printf("ERROR: Could not generate final concat list %s: %s\n", listPath, err) - return false; - } + context := defaultContext() + ok := context.evalMarkutFile(*markutPtr) && context.finishEval() + if !ok { + return false + } - err = ffmpegConcatChunks(listPath, context.outputPath) - if err != nil { - fmt.Printf("ERROR: Could not generated final output %s: %s\n", context.outputPath, err) - return false - } - } + if !*skipcatPtr { - context.PrintSummary() + listPath := "final-list.txt" + err = ffmpegGenerateConcatList(context.chunks, listPath) + if err != nil { + fmt.Printf("ERROR: Could not generate final concat list %s: %s\n", listPath, err) + return false; + } - return true -} + err = ffmpegConcatChunks(listPath, context.outputPath) + if err != nil { + fmt.Printf("ERROR: Could not generated final output %s: %s\n", context.outputPath, err) + return false + } + } -type Subcommand struct { - Name string - Run func(args []string) bool - Description string -} + context.PrintSummary() -var Subcommands = []Subcommand{ - { - Name: "fixup", - Run: fixupSubcommand, - Description: "Fixup the initial footage", - }, - { - Name: "cut", - Run: cutSubcommand, - Description: "Render specific cut of the final video", - }, - { - Name: "chunk", - Run: chunkSubcommand, - Description: "Render specific chunk of the final video", - }, - { - Name: "final", - Run: finalSubcommand, - Description: "Render the final video", - }, - { - Name: "summary", - Run: summarySubcommand, - Description: "Print the summary of the video", - }, - { - Name: "chat", - Run: chatSubcommand, - Description: "Generate chat captions", - }, - { - Name: "prune", - Run: pruneSubcommand, - Description: "Prune unused chunks", - }, - { - Name: "watch", - Run: watchSubcommand, - Description: "Render finished chunks in watch mode every time MARKUT file is modified", + return true + }, }, }