4 コミット 4d72c98c35 ... d4d0cb9568

作者 SHA1 メッセージ 日付
  Gildas Chabot d4d0cb9568 Automatically remove \r from file list 4 年 前
  Gildas Chabot be9c16ecce Add MediaInfo to movies and generate action 4 年 前
  Gildas Chabot 7364d447e1 Add MovieFile and Subtitle file to Movies, fix Movie update 4 年 前
  Gildas Chabot 7db1a5c09e Serve files 4 年 前
共有6 個のファイルを変更した142 個の追加15 個の削除を含む
  1. 50 0
      ffmpeg/ffmpeg.go
  2. 5 0
      files.go
  3. 34 1
      movies.go
  4. 43 12
      pages/pages.go
  5. 1 1
      templates/files.html
  6. 9 1
      templates/movie.html

+ 50 - 0
ffmpeg/ffmpeg.go

@@ -0,0 +1,50 @@
1
+package ffmpeg
2
+
3
+import (
4
+	"fmt"
5
+	"os/exec"
6
+	"strings"
7
+)
8
+
9
+const (
10
+	inputFileIdentifier = "INPUT_VIDEO_FILE"
11
+)
12
+
13
+var (
14
+	Debug = true
15
+)
16
+
17
+func MediaInfo(path string) (string, error) {
18
+	out, err := execCommand(
19
+		fmt.Sprintf("-i %s", inputFileIdentifier),
20
+		path)
21
+	if err.Error() == "exit status 1" && strings.HasSuffix(out, "At least one output file must be specified") {
22
+		err = nil
23
+	}
24
+	return out, err
25
+}
26
+
27
+func execCommand(cmdStr, path string) (string, error) {
28
+	cmdSplit := strings.Split(cmdStr, " ")
29
+	for i, s := range cmdSplit {
30
+		if s == inputFileIdentifier {
31
+			cmdSplit[i] = path
32
+		}
33
+	}
34
+
35
+	cmd := exec.Command("ffmpeg", cmdSplit...)
36
+
37
+	if Debug {
38
+		fmt.Println("Executing:", cmd)
39
+	}
40
+	out, err := cmd.CombinedOutput()
41
+	if err != nil {
42
+		fmt.Printf("Error: %#v\n", err.Error())
43
+		return strings.TrimSuffix(string(out), "\n"), err
44
+	}
45
+	if Debug {
46
+		fmt.Printf("Output: %s\n", string(out))
47
+	}
48
+
49
+	return strings.TrimSuffix(string(out), "\n"), err
50
+}

+ 5 - 0
files.go

@@ -2,6 +2,7 @@ package movies
2
 
2
 
3
 import (
3
 import (
4
 	"fmt"
4
 	"fmt"
5
+	"net/http"
5
 	"os"
6
 	"os"
6
 	"path/filepath"
7
 	"path/filepath"
7
 	"strings"
8
 	"strings"
@@ -61,3 +62,7 @@ func stringContainsAll(path string, split []string) bool {
61
 	}
62
 	}
62
 	return true
63
 	return true
63
 }
64
 }
65
+
66
+func (fs *FileSource) Serve(w http.ResponseWriter, r *http.Request, file string) {
67
+	http.ServeFile(w, r, file)
68
+}

+ 34 - 1
movies.go

@@ -2,8 +2,11 @@ package movies
2
 
2
 
3
 import (
3
 import (
4
 	"encoding/json"
4
 	"encoding/json"
5
+	"fmt"
5
 	"strconv"
6
 	"strconv"
6
 	"strings"
7
 	"strings"
8
+
9
+	"gogs.gildas.ch/gildas/movies/ffmpeg"
7
 )
10
 )
8
 
11
 
9
 type Movie struct {
12
 type Movie struct {
@@ -16,7 +19,10 @@ type Movie struct {
16
 	Year     string
19
 	Year     string
17
 	Runtime  string
20
 	Runtime  string
18
 
21
 
19
-	Files []string
22
+	Files     []string
23
+	MovieFile string
24
+	MediaInfo string
25
+	Subtitles string
20
 
26
 
21
 	OMDB OMDBMovie
27
 	OMDB OMDBMovie
22
 }
28
 }
@@ -59,6 +65,19 @@ type OMDBRating struct {
59
 }
65
 }
60
 
66
 
61
 func Unmarshal(b []byte) (*Movie, error) {
67
 func Unmarshal(b []byte) (*Movie, error) {
68
+	var m Movie
69
+	if err := json.Unmarshal(b, &m); err != nil {
70
+		return nil, err
71
+	}
72
+
73
+	if err := m.FillFromOMDB(); err != nil {
74
+		return nil, err
75
+	}
76
+
77
+	return &m, nil
78
+}
79
+
80
+func UnmarshalFromOMDB(b []byte) (*Movie, error) {
62
 	var omdb OMDBMovie
81
 	var omdb OMDBMovie
63
 	if err := json.Unmarshal(b, &omdb); err != nil {
82
 	if err := json.Unmarshal(b, &omdb); err != nil {
64
 		return nil, err
83
 		return nil, err
@@ -112,3 +131,17 @@ func (m *Movie) FillFromOMDB() error {
112
 
131
 
113
 	return nil
132
 	return nil
114
 }
133
 }
134
+
135
+func (m *Movie) GenerateMediaInfo() error {
136
+	if m.MovieFile == "" {
137
+		return fmt.Errorf("no movie file set")
138
+	}
139
+
140
+	mediaInfo, err := ffmpeg.MediaInfo(m.MovieFile)
141
+	if err != nil {
142
+		return fmt.Errorf("error generating media info from %q: %w", m.MovieFile, err)
143
+	}
144
+
145
+	m.MediaInfo = mediaInfo
146
+	return nil
147
+}

+ 43 - 12
pages/pages.go

@@ -18,6 +18,7 @@ func Router(c *movies.Collection) http.HandlerFunc {
18
 	movieHandler := Movie(c)
18
 	movieHandler := Movie(c)
19
 	listHandler := List(c)
19
 	listHandler := List(c)
20
 	filesHandler := Files(c)
20
 	filesHandler := Files(c)
21
+	fileHandler := File(c, "/files/")
21
 
22
 
22
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
23
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
23
 		path := r.URL.Path
24
 		path := r.URL.Path
@@ -25,9 +26,6 @@ func Router(c *movies.Collection) http.HandlerFunc {
25
 		case path == "/":
26
 		case path == "/":
26
 			homeHandler(w, r)
27
 			homeHandler(w, r)
27
 			return
28
 			return
28
-		case path == "/files":
29
-			filesHandler(w, r)
30
-			return
31
 		case path == "/style.css":
29
 		case path == "/style.css":
32
 			http.ServeFile(w, r, "templates/style.css")
30
 			http.ServeFile(w, r, "templates/style.css")
33
 			return
31
 			return
@@ -37,6 +35,12 @@ func Router(c *movies.Collection) http.HandlerFunc {
37
 		case strings.HasPrefix(path, "/tt"):
35
 		case strings.HasPrefix(path, "/tt"):
38
 			movieHandler(w, r)
36
 			movieHandler(w, r)
39
 			return
37
 			return
38
+		case path == "/files":
39
+			filesHandler(w, r)
40
+			return
41
+		case strings.HasPrefix(path, "/files/"):
42
+			fileHandler(w, r)
43
+			return
40
 		}
44
 		}
41
 	})
45
 	})
42
 }
46
 }
@@ -53,7 +57,7 @@ func Home(c *movies.Collection) http.HandlerFunc {
53
 
57
 
54
 		if r.Method == "POST" {
58
 		if r.Method == "POST" {
55
 			if r.FormValue("omdb_json") != "" {
59
 			if r.FormValue("omdb_json") != "" {
56
-				m, err := movies.Unmarshal([]byte(r.FormValue("omdb_json")))
60
+				m, err := movies.UnmarshalFromOMDB([]byte(r.FormValue("omdb_json")))
57
 				if err != nil {
61
 				if err != nil {
58
 					w.WriteHeader(http.StatusBadRequest)
62
 					w.WriteHeader(http.StatusBadRequest)
59
 					errs = append(errs, err)
63
 					errs = append(errs, err)
@@ -79,12 +83,14 @@ func Home(c *movies.Collection) http.HandlerFunc {
79
 			}
83
 			}
80
 		}
84
 		}
81
 
85
 
82
-		t.Execute(w, map[string]interface{}{
86
+		if err := t.Execute(w, map[string]interface{}{
83
 			"Collection": c,
87
 			"Collection": c,
84
 			"IMDBID":     r.URL.Query().Get("imdb_id"),
88
 			"IMDBID":     r.URL.Query().Get("imdb_id"),
85
 			"OMDBString": omdbString,
89
 			"OMDBString": omdbString,
86
 			"Errors":     errs,
90
 			"Errors":     errs,
87
-		})
91
+		}); err != nil {
92
+			fmt.Println(err)
93
+		}
88
 	})
94
 	})
89
 }
95
 }
90
 
96
 
@@ -121,14 +127,25 @@ func Movie(c *movies.Collection) http.HandlerFunc {
121
 					errs = append(errs, fmt.Errorf("you cannot change the imdb id."))
127
 					errs = append(errs, fmt.Errorf("you cannot change the imdb id."))
122
 				} else {
128
 				} else {
123
 					m = updated
129
 					m = updated
130
+					fmt.Println("Update with", m)
124
 					c.Update(updated)
131
 					c.Update(updated)
125
 				}
132
 				}
126
 			}
133
 			}
127
 
134
 
128
 			if files := r.FormValue("files"); files != "" {
135
 			if files := r.FormValue("files"); files != "" {
136
+				files = strings.ReplaceAll(files, "\r", "")
129
 				m.Files = strings.Split(files, "\n")
137
 				m.Files = strings.Split(files, "\n")
138
+				fmt.Println("Update with", m)
130
 				c.Update(m)
139
 				c.Update(m)
131
 			}
140
 			}
141
+
142
+			if r.FormValue("generate_mediainfo") != "" {
143
+				if err := m.GenerateMediaInfo(); err != nil {
144
+					errs = append(errs, err)
145
+				} else {
146
+					c.Update(m)
147
+				}
148
+			}
132
 		}
149
 		}
133
 
150
 
134
 		fileQuery := r.URL.Query().Get("file_query")
151
 		fileQuery := r.URL.Query().Get("file_query")
@@ -142,14 +159,16 @@ func Movie(c *movies.Collection) http.HandlerFunc {
142
 			errs = append(errs, err)
159
 			errs = append(errs, err)
143
 		}
160
 		}
144
 
161
 
145
-		t.Execute(w, map[string]interface{}{
162
+		if err := t.Execute(w, map[string]interface{}{
146
 			"Movie":       m,
163
 			"Movie":       m,
147
 			"MovieJSON":   string(b),
164
 			"MovieJSON":   string(b),
148
 			"Files":       strings.Join(m.Files, "\n"),
165
 			"Files":       strings.Join(m.Files, "\n"),
149
 			"FileQuery":   fileQuery,
166
 			"FileQuery":   fileQuery,
150
 			"FileResults": fileResults,
167
 			"FileResults": fileResults,
151
 			"Errors":      errs,
168
 			"Errors":      errs,
152
-		})
169
+		}); err != nil {
170
+			fmt.Println(err)
171
+		}
153
 	})
172
 	})
154
 }
173
 }
155
 
174
 
@@ -200,7 +219,7 @@ func List(c *movies.Collection) http.HandlerFunc {
200
 			}
219
 			}
201
 		}
220
 		}
202
 
221
 
203
-		t.Execute(w, map[string]interface{}{
222
+		if err := t.Execute(w, map[string]interface{}{
204
 			"List": l,
223
 			"List": l,
205
 			"Description": template.HTML(
224
 			"Description": template.HTML(
206
 				blackfriday.Run(
225
 				blackfriday.Run(
@@ -209,7 +228,9 @@ func List(c *movies.Collection) http.HandlerFunc {
209
 			"Movies":   ms,
228
 			"Movies":   ms,
210
 			"ListJSON": string(b),
229
 			"ListJSON": string(b),
211
 			"Errors":   errs,
230
 			"Errors":   errs,
212
-		})
231
+		}); err != nil {
232
+			fmt.Println(err)
233
+		}
213
 	})
234
 	})
214
 }
235
 }
215
 
236
 
@@ -238,9 +259,19 @@ func Files(c *movies.Collection) http.HandlerFunc {
238
 			}
259
 			}
239
 		}
260
 		}
240
 
261
 
241
-		t.Execute(w, map[string]interface{}{
262
+		if err := t.Execute(w, map[string]interface{}{
242
 			"Files":  c.AllFiles(),
263
 			"Files":  c.AllFiles(),
243
 			"Errors": errs,
264
 			"Errors": errs,
244
-		})
265
+		}); err != nil {
266
+			fmt.Println(err)
267
+		}
268
+	})
269
+}
270
+
271
+func File(c *movies.Collection, prefix string) http.HandlerFunc {
272
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
273
+		file := "/" + strings.TrimPrefix(r.URL.Path, prefix)
274
+
275
+		c.Files.Serve(w, r, file)
245
 	})
276
 	})
246
 }
277
 }

+ 1 - 1
templates/files.html

@@ -18,7 +18,7 @@
18
 
18
 
19
         <ul class="file-list">
19
         <ul class="file-list">
20
             {{ range $f := .Files }}
20
             {{ range $f := .Files }}
21
-            <li>{{ $f }}</li>
21
+            <li><a href="/files/{{ $f }}">{{ $f }}</a></li>
22
             {{ end }}
22
             {{ end }}
23
         </ul>
23
         </ul>
24
     </body>
24
     </body>

+ 9 - 1
templates/movie.html

@@ -18,12 +18,20 @@
18
             <div>{{ .Movie.Director }}</div>
18
             <div>{{ .Movie.Director }}</div>
19
         </div>
19
         </div>
20
 
20
 
21
+        <p>Movie file: <a href="/files/{{ .Movie.MovieFile }}">{{ .Movie.MovieFile }}</a></p>
22
+
23
+        <pre>{{ .Movie.MediaInfo }}</pre>
24
+
21
         <ul class="file-list">
25
         <ul class="file-list">
22
             {{ range $f := .Movie.Files }}
26
             {{ range $f := .Movie.Files }}
23
-            <li>{{ $f }}</li>
27
+            <li><a href="/files/{{ $f }}">{{ $f }}</a></li>
24
             {{ end }}
28
             {{ end }}
25
         </ul>
29
         </ul>
26
 
30
 
31
+        <div>
32
+            <form method="post"><input type="submit" name="generate_mediainfo" value="Generate MediaInfo" /></form>
33
+        </div>
34
+
27
         <div>
35
         <div>
28
             <form method="post">
36
             <form method="post">
29
                 <p>Update file list: <input type="submit" /><br />
37
                 <p>Update file list: <input type="submit" /><br />