diff options
| author | Dmitry Ilvokhin <d@ilvokhin.com> | 2025-12-16 18:06:41 +0000 |
|---|---|---|
| committer | Dmitry Ilvokhin <d@ilvokhin.com> | 2025-12-16 18:08:36 +0000 |
| commit | a9c35dec90c02f411a1665aae3a6d96876879e88 (patch) | |
| tree | 801832488a9a58de87c1410f96ab3340ca5112d8 /epgpruner | |
| parent | c0a523b13fa011a3bb766de3311c2ce798d0dc5f (diff) | |
| download | misc-a9c35dec90c02f411a1665aae3a6d96876879e88.tar.gz misc-a9c35dec90c02f411a1665aae3a6d96876879e88.tar.bz2 misc-a9c35dec90c02f411a1665aae3a6d96876879e88.zip | |
Optimize memory usage of epgtrim/epgpruner
Do not read entire file into the memory. Instead filter junk on the fly
while reading the file.
Diffstat (limited to 'epgpruner')
| -rw-r--r-- | epgpruner/epgpruner.go | 206 |
1 files changed, 0 insertions, 206 deletions
diff --git a/epgpruner/epgpruner.go b/epgpruner/epgpruner.go deleted file mode 100644 index b2a4f54..0000000 --- a/epgpruner/epgpruner.go +++ /dev/null @@ -1,206 +0,0 @@ -package main - -import ( - "compress/gzip" - "encoding/xml" - "flag" - "fmt" - "io" - "log" - "net/http" - "os" - "regexp" - "strings" -) - -var ( - docType = `<!DOCTYPE tv SYSTEM "xmltv.dtd">` - - epgRE = regexp.MustCompile(`url-tvg="([a-z0-9-:/.]+)"`) - channelRE = regexp.MustCompile(`tvg-id="([a-z0-9-]+)"`) -) - -func usage() { - fmt.Fprintf(os.Stderr, "usage: epgpruner URL\n") - flag.PrintDefaults() - os.Exit(2) -} - -func main() { - flag.Usage = usage - flag.Parse() - if flag.NArg() != 1 { - usage() - os.Exit(1) - } - playlistURL := flag.Args()[0] - playlist := Playlist{ - URL: playlistURL, - } - err := playlist.Fetch() - if err != nil { - log.Fatal(err) - } - - playlist.Reduce() - - reducedEpg, err := xml.MarshalIndent(playlist.Epg, "", " ") - if err != nil { - log.Fatal(err) - } - fmt.Println( - xml.Header + - docType + "\n" + - string(reducedEpg), - ) -} - -type Playlist struct { - URL string - EpgURL string - Channels map[string]struct{} - Epg Epg -} - -func (p *Playlist) Fetch() error { - compressed := false - body, err := fetch(p.URL, compressed) - if err != nil { - return err - } - err = p.parse(body) - if err != nil { - return err - } - - compressed = true - body, err = fetch(p.EpgURL, compressed) - if err != nil { - return err - } - - err = xml.Unmarshal(body, &p.Epg) - if err != nil { - return err - } - - return nil -} - -func fetch(url string, compressed bool) ([]byte, error) { - resp, err := http.Get(url) - if err != nil { - return make([]byte, 0), err - } - if resp.StatusCode != http.StatusOK { - return make([]byte, 0), - fmt.Errorf("Bad reponse status: %d", resp.StatusCode) - } - reader := resp.Body - defer reader.Close() - if compressed { - reader, err = gzip.NewReader(reader) - if err != nil { - return make([]byte, 0), err - } - } - body, err := io.ReadAll(reader) - if err != nil { - return make([]byte, 0), err - } - return body, nil -} - -func (p *Playlist) parse(body []byte) error { - epgURL := "" - channels := make(map[string]struct{}) - for _, line := range strings.Split(string(body), "\n") { - if strings.HasPrefix(line, "#EXTM3U") { - m := epgRE.FindStringSubmatch(line) - if m == nil { - return fmt.Errorf( - "Couldn't match EPG url from: `%s`", - line, - ) - } - epgURL = m[1] - } else if strings.HasPrefix(line, "#EXTINF") { - m := channelRE.FindStringSubmatch(line) - if m == nil { - return fmt.Errorf( - "Couldn't match channel name from: `%s`", - line, - ) - } - channel := m[1] - channels[channel] = struct{}{} - } - } - - p.EpgURL = epgURL - p.Channels = channels - - return nil -} - -func (p *Playlist) Reduce() { - channels := make([]Channel, 0) - programmes := make([]Programme, 0) - - for _, channel := range p.Epg.Channels { - if _, ok := p.Channels[channel.ID]; !ok { - continue - } - channels = append(channels, channel) - } - - for _, programme := range p.Epg.Programmes { - if _, ok := p.Channels[programme.Channel]; !ok { - continue - } - programmes = append(programmes, programme) - } - - p.Epg.Channels = channels - p.Epg.Programmes = programmes -} - -type Epg struct { - XMLName xml.Name `xml:"tv"` - Info string `xml:"generator-info-name,attr"` - Channels []Channel `xml:"channel"` - Programmes []Programme `xml:"programme"` -} - -type Channel struct { - ID string `xml:"id,attr"` - DisplayName DisplayName `xml:"display-name"` - Icon Icon `xml:"icon"` -} - -type DisplayName struct { - Lang string `xml:"lang,attr"` - Name string `xml:",chardata"` -} - -type Icon struct { - Src string `xml:"src,attr"` -} - -type Programme struct { - Start string `xml:"start,attr"` - Stop string `xml:"stop,attr"` - Channel string `xml:"channel,attr"` - Title Title `xml:"title,omitempty"` - Desc Desc `xml:"desc,omitempty"` -} - -type Title struct { - Lang string `xml:"lang,attr"` - Title string `xml:",chardata"` -} - -type Desc struct { - Lang string `xml:"lang,attr"` - Desc string `xml:",chardata"` -} |