@@ -21,10 +21,12 @@ import (
2121 "io"
2222 "os"
2323 "path/filepath"
24+ "strconv"
2425 "text/tabwriter"
2526
2627 "github.com/muesli/coral"
2728 "github.com/rs/zerolog/log"
29+ "github.com/spf13/viper"
2830)
2931
3032var pltPrintJSON bool
@@ -63,7 +65,7 @@ func runPltPrint(_ *coral.Command, args []string) {
6365 log .Fatal ().Err (err ).Msg ("Could not parse playlist" )
6466 }
6567
66- view := buildPrintView (pl )
68+ view := buildPrintView (pl , viper . GetString ( "plt.mediaapi" ) )
6769
6870 if pltPrintJSON {
6971 err = renderJSON (os .Stdout , view )
@@ -132,6 +134,7 @@ type printItem struct {
132134 Kind string `json:"kind"`
133135 DurationSec float64 `json:"durationSec"`
134136 EndAction int `json:"endAction"`
137+ MediaURL string `json:"mediaURL,omitempty"`
135138 Markers []printMarker `json:"markers,omitempty"`
136139}
137140
@@ -141,8 +144,11 @@ type printMarker struct {
141144 DurationSec float64 `json:"durationSec"`
142145}
143146
144- // buildPrintView projects the parsed playlist into the print summary.
145- func buildPrintView (pl * Playlist ) printView {
147+ // buildPrintView projects the parsed playlist into the print summary. base is
148+ // the configured media API endpoint (plt.mediaapi); when empty, media URLs are
149+ // shown with a "<plt.mediaapi>" placeholder so the playlist-derived query is
150+ // still visible offline.
151+ func buildPrintView (pl * Playlist , base string ) printView {
146152 view := printView {Name : pl .Name , Items : make ([]printItem , 0 , len (pl .Items ))}
147153
148154 for _ , it := range pl .Items {
@@ -153,6 +159,7 @@ func buildPrintView(pl *Playlist) printView {
153159 Kind : itemKind (it ),
154160 DurationSec : itemDurationSec (it ),
155161 EndAction : it .EndAction ,
162+ MediaURL : itemMediaURL (it , base ),
156163 }
157164 for _ , m := range it .Markers {
158165 pi .Markers = append (pi .Markers , printMarker {
@@ -166,6 +173,33 @@ func buildPrintView(pl *Playlist) printView {
166173 return view
167174}
168175
176+ // itemMediaURL builds the media API query URL an item resolves to, derived from
177+ // its catalog keys. Image cues and unsupported shapes have no URL. When base is
178+ // empty the placeholder "<plt.mediaapi>" stands in for the configured endpoint.
179+ func itemMediaURL (it Item , base string ) string {
180+ if it .Location == nil {
181+ return ""
182+ }
183+ if base == "" {
184+ base = "<plt.mediaapi>"
185+ }
186+
187+ url , err := buildMediaURL (base , displayLangCode (it .Location .MepsLanguage ), it .Location )
188+ if err != nil {
189+ return ""
190+ }
191+ return url
192+ }
193+
194+ // displayLangCode returns the written-language code for a MepsLanguage ID, or
195+ // the numeric ID when it is not mapped (best effort for display only).
196+ func displayLangCode (id int ) string {
197+ if code , err := resolveLanguage (id , "" ); err == nil {
198+ return code
199+ }
200+ return strconv .Itoa (id )
201+ }
202+
169203// describeSource renders a one-line description of where an item's media comes
170204// from, following the catalog resolution rule (KeySymbol+Track, book/chapter, docid).
171205func describeSource (it Item ) string {
@@ -178,12 +212,17 @@ func describeSource(it Item) string {
178212 return "unknown source"
179213 }
180214
181- switch {
182- case loc .KeySymbol == "nwt" && loc .BookNumber > 0 :
215+ shape , err := classifyLocation (loc )
216+ if err != nil {
217+ return fmt .Sprintf ("unsupported location (type %d)" , loc .Type )
218+ }
219+
220+ switch shape {
221+ case shapeBookChapter :
183222 return fmt .Sprintf ("book %d:%d" , loc .BookNumber , loc .ChapterNumber )
184- case loc . KeySymbol != "" && loc . Track > 0 :
223+ case shapePub :
185224 return fmt .Sprintf ("pub %s track %d" , loc .KeySymbol , loc .Track )
186- case loc . Type == 3 && loc . DocumentID > 0 :
225+ case shapeDocid :
187226 return fmt .Sprintf ("docid %d" , loc .DocumentID )
188227 default :
189228 return fmt .Sprintf ("unsupported location (type %d)" , loc .Type )
@@ -226,7 +265,7 @@ func renderText(w io.Writer, view printView) error {
226265 }
227266
228267 tw := tabwriter .NewWriter (w , 0 , 2 , 2 , ' ' , 0 )
229- if _ , err := fmt .Fprintln (tw , "#\t LABEL\t SOURCE\t DURATION\t MARKERS\t END " ); err != nil {
268+ if _ , err := fmt .Fprintln (tw , "#\t LABEL\t SOURCE\t DURATION\t MARKERS\t AFTER " ); err != nil {
230269 return fmt .Errorf ("could not write table header: %w" , err )
231270 }
232271
@@ -235,15 +274,40 @@ func renderText(w io.Writer, view printView) error {
235274 if len (it .Markers ) > 0 {
236275 markers = fmt .Sprintf ("%d" , len (it .Markers ))
237276 }
238- if _ , err := fmt .Fprintf (tw , "%d\t %s\t %s\t %.1fs\t %s\t %d \n " ,
239- it .Position , it .Label , it .Source , it .DurationSec , markers , it .EndAction ); err != nil {
277+ if _ , err := fmt .Fprintf (tw , "%d\t %s\t %s\t %.1fs\t %s\t %s \n " ,
278+ it .Position , it .Label , it .Source , it .DurationSec , markers , endActionLabel ( it .EndAction ) ); err != nil {
240279 return fmt .Errorf ("could not write table row: %w" , err )
241280 }
242281 }
243282
244283 if err := tw .Flush (); err != nil {
245284 return fmt .Errorf ("could not flush table: %w" , err )
246285 }
286+
287+ return renderMediaURLs (w , view )
288+ }
289+
290+ // renderMediaURLs prints the media API query URL each item resolves to, derived
291+ // from the playlist's catalog keys.
292+ func renderMediaURLs (w io.Writer , view printView ) error {
293+ var withURL []printItem
294+ for _ , it := range view .Items {
295+ if it .MediaURL != "" {
296+ withURL = append (withURL , it )
297+ }
298+ }
299+ if len (withURL ) == 0 {
300+ return nil
301+ }
302+
303+ if _ , err := fmt .Fprint (w , "\n Media URLs:\n " ); err != nil {
304+ return fmt .Errorf ("could not write media URL header: %w" , err )
305+ }
306+ for _ , it := range withURL {
307+ if _ , err := fmt .Fprintf (w , " %d %s\n " , it .Position , it .MediaURL ); err != nil {
308+ return fmt .Errorf ("could not write media URL: %w" , err )
309+ }
310+ }
247311 return nil
248312}
249313
0 commit comments