Skip to content

Commit 89aba07

Browse files
authored
Merge pull request #3 from kindlyops/elliot/chaptersplit
2 parents b8bca87 + 14d3ece commit 89aba07

29 files changed

Lines changed: 8845 additions & 18 deletions

cmd/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ go_library(
99
importpath = "github.com/kindlyops/vbs/cmd",
1010
visibility = ["//visibility:public"],
1111
deps = [
12+
"//vendor/github.com/kennygrant/sanitize:go_default_library",
1213
"//vendor/github.com/mitchellh/go-homedir:go_default_library",
1314
"//vendor/github.com/spf13/cobra:go_default_library",
1415
"//vendor/github.com/spf13/viper:go_default_library",

cmd/chapters.go

Lines changed: 138 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,37 @@
1515
package cmd
1616

1717
import (
18+
"encoding/json"
19+
"fmt"
1820
"log"
1921
"os"
2022
"os/exec"
23+
"path"
24+
"path/filepath"
25+
"strings"
26+
"sync"
2127

28+
"github.com/kennygrant/sanitize"
2229
"github.com/spf13/cobra"
2330
)
2431

25-
// lastUsedCmd represents the lastUsed command
2632
var chapterListCmd = &cobra.Command{
27-
Use: "chapterlist",
33+
Use: "chapterlist <videofile.mp4>",
2834
Short: "List chapters in a video container.",
2935
Long: `Use ffprobe to discover all chapter metadata in a video file container.`,
3036
Run: chapterList,
3137
Args: cobra.ExactArgs(1),
3238
}
3339

34-
func chapterList(cmd *cobra.Command, args []string) {
40+
var chapterSplitCmd = &cobra.Command{
41+
Use: "chaptersplit <videofile.mp4>",
42+
Short: "Split video file into separate files per chapter.",
43+
Long: `Use ffmpeg to copy each chapter from a video file into it's own file.`,
44+
Run: chapterSplit,
45+
Args: cobra.ExactArgs(1),
46+
}
47+
48+
func chapterSplit(cmd *cobra.Command, args []string) {
3549

3650
_, err := exec.LookPath("ffprobe")
3751

@@ -45,39 +59,145 @@ func chapterList(cmd *cobra.Command, args []string) {
4559
log.Fatal("Could not find ffmpeg. Please install ffmpeg.")
4660
}
4761

48-
_, err = exec.LookPath("jq")
62+
target := args[0]
63+
_, err = os.Stat(target)
4964

5065
if err != nil {
51-
log.Fatal("Could not find jq. Please install jq.")
66+
log.Fatal("Could not access video container ", target)
5267
}
5368

54-
target := args[0]
55-
_, err = os.Stat(target)
69+
data, err := getChapters(target)
70+
base := strings.Trim(path.Base(target), path.Ext(target))
71+
targetdir := fmt.Sprintf("split_%s", base)
72+
err = os.MkdirAll(targetdir, 0777)
5673

5774
if err != nil {
58-
log.Fatal("Could not access video container ", target)
75+
log.Fatal(err)
76+
}
77+
78+
var wg sync.WaitGroup
79+
80+
for _, c := range data.Chapters {
81+
wg.Add(1)
82+
go copyChapter(&wg, c, target, targetdir)
5983
}
6084

85+
wg.Wait()
86+
}
87+
88+
func copyChapter(wg *sync.WaitGroup, c chapter, sourcefile, targetdir string) error {
89+
defer wg.Done()
90+
91+
title := strings.Trim(c.Tags.Title, " \n\r")
92+
safetitle := sanitize.Name(title)
93+
prefix := fmt.Sprintf("%03d_", c.Id)
94+
outfile := filepath.Join(targetdir, prefix+safetitle+path.Ext(sourcefile))
95+
96+
cmd := exec.Command("ffmpeg",
97+
"-loglevel", "error",
98+
"-i", sourcefile,
99+
"-c", "copy",
100+
"-map", "0",
101+
"-ss", c.StartTime,
102+
"-to", c.EndTime,
103+
outfile)
104+
105+
output, err := cmd.CombinedOutput()
106+
107+
if err != nil {
108+
fmt.Printf("%s: %s\n", outfile, output)
109+
}
110+
111+
return err
112+
}
113+
114+
// sample json output
115+
// {
116+
// "chapters": [
117+
// {
118+
// "id": 0,
119+
// "time_base": "1/1000",
120+
// "start": 0,
121+
// "start_time": "0.000000",
122+
// "end": 6006,
123+
// "end_time": "6.006000",
124+
// "tags": {
125+
// "title": "Title Page\r"
126+
// }
127+
// }
128+
// ]
129+
// }
130+
131+
type tags struct {
132+
Title string `json:"title"`
133+
}
134+
135+
type chapter struct {
136+
StartTime string `json:"start_time"`
137+
EndTime string `json:"end_time"`
138+
Id int `json:"id"`
139+
Tags tags `json:"tags"`
140+
}
141+
142+
type ffmprobeResponse struct {
143+
Chapters []chapter `json:"chapters"`
144+
}
145+
146+
func getChapters(target string) (ffmprobeResponse, error) {
61147
command := exec.Command("ffprobe",
62148
"-print_format", "json",
63149
"-loglevel", "error",
64150
"-show_chapters",
65151
"-i", target)
66152

67-
output, _ := command.Output()
68-
_, _ = os.Stdout.Write(output)
153+
output, err := command.Output()
154+
response := ffmprobeResponse{}
155+
156+
if err != nil {
157+
return response, err
158+
}
159+
160+
err = json.Unmarshal(output, &response)
161+
162+
return response, err
69163
}
70164

71-
func init() {
72-
rootCmd.AddCommand(chapterListCmd)
165+
func chapterList(cmd *cobra.Command, args []string) {
73166

74-
// Here you will define your flags and configuration settings.
167+
_, err := exec.LookPath("ffprobe")
75168

76-
// Cobra supports Persistent Flags which will work for this command
77-
// and all subcommands, e.g.:
78-
// dryrunCmd.PersistentFlags().String("foo", "", "A help for foo")
169+
if err != nil {
170+
log.Fatal("Could not find ffprobe. Please install ffmpeg and ffprobe.")
171+
}
172+
173+
_, err = exec.LookPath("ffmpeg")
79174

80-
// Cobra supports local flags which will only run when this command
81-
// is called directly, e.g.:
175+
if err != nil {
176+
log.Fatal("Could not find ffmpeg. Please install ffmpeg.")
177+
}
82178

179+
target := args[0]
180+
_, err = os.Stat(target)
181+
182+
if err != nil {
183+
log.Fatal("Could not access video container ", target)
184+
}
185+
186+
data, err := getChapters(target)
187+
188+
if err != nil {
189+
log.Fatal("Problem getting chapter data ", err)
190+
}
191+
192+
formattedJSON, err := json.MarshalIndent(data, "", " ")
193+
if err != nil {
194+
log.Fatal(err)
195+
}
196+
197+
_, _ = os.Stdout.Write(formattedJSON)
198+
}
199+
200+
func init() {
201+
rootCmd.AddCommand(chapterListCmd)
202+
rootCmd.AddCommand(chapterSplitCmd)
83203
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.12
55
require (
66
// If changing rules_go version, remember to change version in WORKSPACE also
77
github.com/bazelbuild/rules_go v0.23.1
8+
github.com/kennygrant/sanitize v1.2.4
89
github.com/mitchellh/go-homedir v1.1.0
910
github.com/spf13/cobra v0.0.7
1011
github.com/spf13/viper v1.7.0

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
107107
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
108108
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
109109
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
110+
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
111+
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
110112
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
111113
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
112114
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -238,6 +240,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
238240
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
239241
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
240242
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
243+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
241244
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
242245
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
243246
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=

vendor/github.com/kennygrant/sanitize/.gitignore

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/kennygrant/sanitize/.travis.yml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/kennygrant/sanitize/BUILD.bazel

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/kennygrant/sanitize/LICENSE

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/kennygrant/sanitize/README.md

Lines changed: 62 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)