diff --git a/Makefile b/Makefile index d1e6956c..926e1e90 100644 --- a/Makefile +++ b/Makefile @@ -72,10 +72,12 @@ SRCS = src/main.c \ src/rawtsinput.c \ src/iptv_input.c \ src/avc.c \ + src/huffman.c \ SRCS += src/epggrab/pyepg.c\ src/epggrab/xmltv.c\ - src/epggrab/eit.c + src/epggrab/eit.c \ + src/epggrab/opentv.c SRCS += src/plumbing/tsfix.c \ src/plumbing/globalheaders.c \ diff --git a/data/epggrab/opentv/dict/skyeng b/data/epggrab/opentv/dict/skyeng new file mode 100644 index 00000000..076835e2 --- /dev/null +++ b/data/epggrab/opentv/dict/skyeng @@ -0,0 +1 @@ +[{"code": "101010111000110000101001100", "data": "\u0000"}, {"code": "101010111000110000101001101", "data": "\u0001"}, {"code": "101010111000110000101001110", "data": "\u0002"}, {"code": "101010111000110000101001111", "data": "\u0003"}, {"code": "101010111000110000101010000", "data": "\u0004"}, {"code": "101010111000110000101010001", "data": "\u0005"}, {"code": "101010111000110000101010010", "data": "\u0006"}, {"code": "101010111000110000101010011", "data": "\u0007"}, {"code": "101010111000110000101010100", "data": "\b"}, {"code": "0001000", "data": "\t"}, {"code": "1110111", "data": "\n"}, {"code": "101010111000110000101010101", "data": "\u000b"}, {"code": "101010111000110000101010110", "data": "\f"}, {"code": "101010111000110000101010111", "data": "\r"}, {"code": "101010111000110000101011000", "data": "\u000e"}, {"code": "101010111000110000101011001", "data": "\u000f"}, {"code": "101010111000110000101011010", "data": "\u0010"}, {"code": "101010111000110000101011011", "data": "\u0011"}, {"code": "101010111000110000101011100", "data": "\u0012"}, {"code": "101010111000110000101011101", "data": "\u0013"}, {"code": "101010111000110000101011110", "data": "\u0014"}, {"code": "101010111000110000101011111", "data": "\u0015"}, {"code": "101010111000110000101100000", "data": "\u0016"}, {"code": "101010111000110000101100001", "data": "\u0017"}, {"code": "101010111000110000101100010", "data": "\u0018"}, {"code": "101010111000110000101100011", "data": "\u0019"}, {"code": "101010111000110000101100100", "data": "\u001a"}, {"code": "101010111000110000101100101", "data": "\u001b"}, {"code": "101010111000110000101100110", "data": "\u001c"}, {"code": "101010111000110000101100111", "data": "\u001d"}, {"code": "101010111000110000101101000", "data": "\u001e"}, {"code": "101010111000110000101101001", "data": "\u001f"}, {"code": "110", "data": " "}, {"code": "01000011000", "data": "!"}, {"code": "101010111000110000101101010", "data": "\""}, {"code": "101010111000110000101101011", "data": "#"}, {"code": "1010101110001101", "data": "$"}, {"code": "101010111000110000101101100", "data": "%"}, {"code": "10000011101", "data": "&"}, {"code": "10000010", "data": "'"}, {"code": "11101000101", "data": "("}, {"code": "1010101100", "data": ")"}, {"code": "100010101110", "data": "*"}, {"code": "101010111000110000101101101", "data": "+"}, {"code": "1011000", "data": ","}, {"code": "10001011", "data": "-"}, {"code": "1110110", "data": "."}, {"code": "00010100011110", "data": "/"}, {"code": "111010010", "data": "0"}, {"code": "101100111", "data": "1"}, {"code": "1000101000", "data": "2"}, {"code": "1000001111", "data": "3"}, {"code": "0001010000", "data": "4"}, {"code": "1110101000", "data": "5"}, {"code": "1000101001", "data": "6"}, {"code": "1000100010", "data": "7"}, {"code": "10001010110", "data": "8"}, {"code": "0100001101", "data": "9"}, {"code": "11101000110", "data": ":"}, {"code": "00010100010", "data": ";"}, {"code": "1110100011111", "data": "<"}, {"code": "101010111000110000101101110", "data": "="}, {"code": "1110101001100", "data": ">"}, {"code": "111010100111", "data": "?"}, {"code": "101010111000110001", "data": "@"}, {"code": "11100010", "data": "A"}, {"code": "01000000", "data": "B"}, {"code": "01000010", "data": "C"}, {"code": "111000111", "data": "D"}, {"code": "1110100000", "data": "E"}, {"code": "101010100", "data": "F"}, {"code": "100010000", "data": "G"}, {"code": "101010101", "data": "H"}, {"code": "1110100001", "data": "I"}, {"code": "000101001", "data": "J"}, {"code": "1110100111", "data": "K"}, {"code": "100000110", "data": "L"}, {"code": "10001001", "data": "M"}, {"code": "111010111", "data": "N"}, {"code": "010000010", "data": "O"}, {"code": "00010101", "data": "P"}, {"code": "1000101010111", "data": "Q"}, {"code": "111010110", "data": "R"}, {"code": "0001001", "data": "S"}, {"code": "0001011", "data": "T"}, {"code": "10101011101", "data": "U"}, {"code": "11101010101", "data": "V"}, {"code": "10110010", "data": "W"}, {"code": "1110001101101111", "data": "X"}, {"code": "10101011110", "data": "Y"}, {"code": "1110101010000", "data": "Z"}, {"code": "10101011100011001", "data": "["}, {"code": "101010111000110000101101111", "data": "\\"}, {"code": "11100011011011100", "data": "]"}, {"code": "101010111000110000101110000", "data": "^"}, {"code": "101010111000110000101110001", "data": "_"}, {"code": "11101010010", "data": "`"}, {"code": "1001", "data": "a"}, {"code": "1110000", "data": "b"}, {"code": "111001", "data": "c"}, {"code": "01001", "data": "d"}, {"code": "1111", "data": "e"}, {"code": "100001", "data": "f"}, {"code": "100011", "data": "g"}, {"code": "10111", "data": "h"}, {"code": "0101", "data": "i"}, {"code": "11100011010", "data": "j"}, {"code": "1000000", "data": "k"}, {"code": "10100", "data": "l"}, {"code": "101011", "data": "m"}, {"code": "0111", "data": "n"}, {"code": "0011", "data": "o"}, {"code": "000111", "data": "p"}, {"code": "10101011011", "data": "q"}, {"code": "0010", "data": "r"}, {"code": "0000", "data": "s"}, {"code": "0110", "data": "t"}, {"code": "101101", "data": "u"}, {"code": "1010100", "data": "v"}, {"code": "000110", "data": "w"}, {"code": "1110101011", "data": "x"}, {"code": "010001", "data": "y"}, {"code": "1011001100", "data": "z"}, {"code": "101010111000110000101110010", "data": "{"}, {"code": "101010111000110000101110011", "data": "|"}, {"code": "101010111000110000101110100", "data": "}"}, {"code": "101010111000110000101110101", "data": "~"}, {"code": "101010111000110000101110110", "data": ""}, {"code": "101010111000110000101110111", "data": "€"}, {"code": "101010111000110000101111000", "data": ""}, {"code": "101010111000110000101111001", "data": "‚"}, {"code": "101010111000110000101111010", "data": "ƒ"}, {"code": "101010111000110000101111011", "data": "„"}, {"code": "101010111000110000101111100", "data": "…"}, {"code": "101010111000110000101111101", "data": "†"}, {"code": "101010111000110000101111110", "data": "‡"}, {"code": "101010111000110000101111111", "data": "ˆ"}, {"code": "101010111000110000110000000", "data": "‰"}, {"code": "101010111000110000110000001", "data": "Š"}, {"code": "101010111000110000110000010", "data": "‹"}, {"code": "101010111000110000110000011", "data": "Œ"}, {"code": "101010111000110000110000100", "data": ""}, {"code": "101010111000110000110000101", "data": "Ž"}, {"code": "101010111000110000110000110", "data": ""}, {"code": "101010111000110000110000111", "data": "€"}, {"code": "101010111000110000110001000", "data": ""}, {"code": "101010111000110000110001001", "data": "‚"}, {"code": "101010111000110000110001010", "data": "ƒ"}, {"code": "101010111000110000110001011", "data": "„"}, {"code": "101010111000110000110001100", "data": "…"}, {"code": "101010111000110000110001101", "data": "†"}, {"code": "101010111000110000110001110", "data": "‡"}, {"code": "101010111000110000110001111", "data": "ˆ"}, {"code": "101010111000110000110010000", "data": "‰"}, {"code": "101010111000110000110010001", "data": "Š"}, {"code": "101010111000110000110010010", "data": "‹"}, {"code": "11100011011011101", "data": "Œ"}, {"code": "101010111000110000110010011", "data": ""}, {"code": "101010111000110000110010100", "data": "Ž"}, {"code": "101010111000110000110010101", "data": ""}, {"code": "101010111000110000110010110", "data": " "}, {"code": "101010111000110000110010111", "data": "¡"}, {"code": "101010111000110000110011000", "data": "¢"}, {"code": "101010111000110000110011001", "data": "£"}, {"code": "101010111000110000110011010", "data": "¤"}, {"code": "101010111000110000110011011", "data": "¥"}, {"code": "101010111000110000110011100", "data": "¦"}, {"code": "101010111000110000110011101", "data": "§"}, {"code": "101010111000110000110011110", "data": "¨"}, {"code": "101010111000110000110011111", "data": "©"}, {"code": "101010111000110000110100000", "data": "ª"}, {"code": "101010111000110000110100001", "data": "«"}, {"code": "101010111000110000110100010", "data": "¬"}, {"code": "101010111000110000110100011", "data": "­"}, {"code": "101010111000110000110100100", "data": "®"}, {"code": "101010111000110000110100101", "data": "¯"}, {"code": "101010111000110000110100110", "data": "°"}, {"code": "101010111000110000110100111", "data": "±"}, {"code": "101010111000110000110101000", "data": "²"}, {"code": "101010111000110000110101001", "data": "³"}, {"code": "101010111000110000110101010", "data": "´"}, {"code": "101010111000110000110101011", "data": "µ"}, {"code": "101010111000110000110101100", "data": "¶"}, {"code": "101010111000110000110101101", "data": "·"}, {"code": "101010111000110000110101110", "data": "¸"}, {"code": "101010111000110000110101111", "data": "¹"}, {"code": "101010111000110000110110000", "data": "º"}, {"code": "101010111000110000110110001", "data": "»"}, {"code": "101010111000110000110110010", "data": "¼"}, {"code": "101010111000110000110110011", "data": "½"}, {"code": "101010111000110000110110100", "data": "¾"}, {"code": "101010111000110000110110101", "data": "¿"}, {"code": "101010111000110000110110110", "data": "À"}, {"code": "101010111000110000110110111", "data": "Á"}, {"code": "101010111000110000110111000", "data": "Â"}, {"code": "101010111000110000110111001", "data": "Ã"}, {"code": "101010111000110000110111010", "data": "Ä"}, {"code": "101010111000110000110111011", "data": "Å"}, {"code": "101010111000110000110111100", "data": "Æ"}, {"code": "101010111000110000110111101", "data": "Ç"}, {"code": "101010111000110000110111110", "data": "È"}, {"code": "101010111000110000110111111", "data": "É"}, {"code": "101010111000110000111000000", "data": "Ê"}, {"code": "101010111000110000111000001", "data": "Ë"}, {"code": "101010111000110000111000010", "data": "Ì"}, {"code": "101010111000110000111000011", "data": "Í"}, {"code": "101010111000110000111000100", "data": "Î"}, {"code": "101010111000110000111000101", "data": "Ï"}, {"code": "101010111000110000111000110", "data": "Ð"}, {"code": "101010111000110000111000111", "data": "Ñ"}, {"code": "101010111000110000111001000", "data": "Ò"}, {"code": "101010111000110000111001001", "data": "Ó"}, {"code": "101010111000110000111001010", "data": "Ô"}, {"code": "101010111000110000111001011", "data": "Õ"}, {"code": "101010111000110000111001100", "data": "Ö"}, {"code": "101010111000110000111001101", "data": "×"}, {"code": "101010111000110000111001110", "data": "Ø"}, {"code": "101010111000110000111001111", "data": "Ù"}, {"code": "101010111000110000111010000", "data": "Ú"}, {"code": "101010111000110000111010001", "data": "Û"}, {"code": "101010111000110000111010010", "data": "Ü"}, {"code": "101010111000110000111010011", "data": "Ý"}, {"code": "101010111000110000111010100", "data": "Þ"}, {"code": "101010111000110000111010101", "data": "ß"}, {"code": "101010111000110000111010110", "data": "à"}, {"code": "101010111000110000111010111", "data": "á"}, {"code": "101010111000110000111011000", "data": "â"}, {"code": "101010111000110000111011001", "data": "ã"}, {"code": "101010111000110000111011010", "data": "ä"}, {"code": "101010111000110000111011011", "data": "å"}, {"code": "101010111000110000111011100", "data": "æ"}, {"code": "101010111000110000111011101", "data": "ç"}, {"code": "101010111000110000111011110", "data": "è"}, {"code": "101010111000110000111011111", "data": "é"}, {"code": "101010111000110000111100000", "data": "ê"}, {"code": "101010111000110000111100001", "data": "ë"}, {"code": "101010111000110000111100010", "data": "ì"}, {"code": "101010111000110000111100011", "data": "í"}, {"code": "101010111000110000111100100", "data": "î"}, {"code": "101010111000110000111100101", "data": "ï"}, {"code": "101010111000110000111100110", "data": "ð"}, {"code": "101010111000110000111100111", "data": "ñ"}, {"code": "101010111000110000111101000", "data": "ò"}, {"code": "101010111000110000111101001", "data": "ó"}, {"code": "101010111000110000111101010", "data": "ô"}, {"code": "101010111000110000111101011", "data": "õ"}, {"code": "101010111000110000111101100", "data": "ö"}, {"code": "101010111000110000111101101", "data": "÷"}, {"code": "101010111000110000111101110", "data": "ø"}, {"code": "101010111000110000111101111", "data": "ù"}, {"code": "101010111000110000111110000", "data": "ú"}, {"code": "101010111000110000111110001", "data": "û"}, {"code": "101010111000110000111110010", "data": "ü"}, {"code": "101010111000110000111110011", "data": "ý"}, {"code": "101010111000110000111110100", "data": "þ"}, {"code": "101010111000110000111110101", "data": "ÿ"}, {"code": "10101011111110", "data": "(Including "}, {"code": "11101000100010", "data": "(New Series)"}, {"code": "1110100011101", "data": "(Part "}, {"code": "1110100110", "data": "(Repeat)"}, {"code": "010000111", "data": "(Stereo)"}, {"code": "010000011", "data": "(Stereo) (Teletext)"}, {"code": "1110001100", "data": "(Teletext)"}, {"code": "100000111001110", "data": "(Widescreen)"}, {"code": "101010111000111", "data": "Action"}, {"code": "10110011011111", "data": "Adventures"}, {"code": "0100001100100", "data": "America"}, {"code": "111010100110111", "data": "Animated"}, {"code": "0100001100101", "data": "Australia"}, {"code": "11101010100010", "data": "Away"}, {"code": "10101011111111", "data": "BBC"}, {"code": "11100011011000", "data": "Baby"}, {"code": "11101010100011", "data": "Best"}, {"code": "10110011011000", "data": "Big"}, {"code": "1000101011111", "data": "Bill"}, {"code": "1000101010000", "data": "Black"}, {"code": "1011001101110", "data": "Blue"}, {"code": "000101000110", "data": "Breakfast"}, {"code": "1010101111100", "data": "Britain"}, {"code": "1110100011100", "data": "British"}, {"code": "0100001100110", "data": "Business"}, {"code": "1010101111101", "data": "Call"}, {"code": "10101011100000", "data": "Cartoon"}, {"code": "10101011100001", "data": "Channel"}, {"code": "11101010100111", "data": "Children"}, {"code": "11100011011001", "data": "Clock"}, {"code": "11101000100011", "data": "Comedy"}, {"code": "111010101001010", "data": "Cook"}, {"code": "111010100110110", "data": "Country"}, {"code": "101010110100", "data": "Directed by "}, {"code": "0100001100111", "data": "Drama"}, {"code": "1000101010001", "data": "East"}, {"code": "100000111001111", "data": "Education"}, {"code": "00010100011111", "data": "English"}, {"code": "0001010001110", "data": "Europe"}, {"code": "10110011011001", "data": "Extra"}, {"code": "10101011100010", "data": "Final"}, {"code": "111000110110100", "data": "Financial"}, {"code": "111000110111", "data": "For"}, {"code": "11101000111101", "data": "French"}, {"code": "1000101010010", "data": "From"}, {"code": "1010101111110", "data": "George"}, {"code": "1000100011010", "data": "Get"}, {"code": "10001000110110", "data": "Girls"}, {"code": "10001000110111", "data": "Golden"}, {"code": "111010101001011", "data": "Golf"}, {"code": "1010101101010", "data": "Good"}, {"code": "11101000100100", "data": "Great"}, {"code": "111010101001100", "data": "Hampshire"}, {"code": "1000101010011", "data": "Headlines"}, {"code": "11101010011010", "data": "Hear"}, {"code": "1000001110000", "data": "Hill"}, {"code": "111000110110101", "data": "Hollywood"}, {"code": "1000101010100", "data": "Home"}, {"code": "11101000100101", "data": "Hour"}, {"code": "1000001110010", "data": "House"}, {"code": "1010101101011", "data": "How"}, {"code": "11101010100100", "data": "ITN"}, {"code": "111010101001101", "data": "Important"}, {"code": "1000101011110", "data": "Including"}, {"code": "11101000100110", "data": "International"}, {"code": "10001000111", "data": "John"}, {"code": "11101000100111", "data": "Last"}, {"code": "10000011100110", "data": "Late"}, {"code": "10001010101100", "data": "Learn"}, {"code": "10001010101101", "data": "Little"}, {"code": "1110100010000", "data": "Live"}, {"code": "11101000111100", "data": "London"}, {"code": "10110011011110", "data": "Look"}, {"code": "111000110110110", "data": "Lunch"}, {"code": "1000101010101", "data": "Man"}, {"code": "1000001110001", "data": "Mark"}, {"code": "101010111001", "data": "Meridian"}, {"code": "1011001101101", "data": "Michael"}, {"code": "101010111000110000111110110", "data": "Minutes"}, {"code": "101010111000110000111110111", "data": "More"}, {"code": "101010111000110000111111000", "data": "Morning"}, {"code": "101010111000110000111111001", "data": "Murder"}, {"code": "101010111000110000111111010", "data": "Nation"}, {"code": "101010111000110000111111011", "data": "Neighbours"}, {"code": "101010111000110000111111100", "data": "New"}, {"code": "101010111000110000111111101", "data": "News & Weather"}, {"code": "101010111000110000111111110", "data": "News And Weather"}, {"code": "101010111000110000111111111", "data": "Paul"}, {"code": "10101011100011000000000000", "data": "Plus"}, {"code": "10101011100011000000000001", "data": "Prayer"}, {"code": "10101011100011000000000010", "data": "Present"}, {"code": "10101011100011000000000011", "data": "Presented by"}, {"code": "10101011100011000000000100", "data": "Quiz"}, {"code": "10101011100011000000000101", "data": "Regional"}, {"code": "10101011100011000000000110", "data": "Represent"}, {"code": "10101011100011000000000111", "data": "Resource"}, {"code": "10101011100011000000001000", "data": "Review"}, {"code": "10101011100011000000001001", "data": "Richard"}, {"code": "10101011100011000000001010", "data": "School"}, {"code": "10101011100011000000001011", "data": "Series"}, {"code": "10101011100011000000001100", "data": "Service"}, {"code": "10101011100011000000001101", "data": "Show"}, {"code": "10101011100011000000001110", "data": "Smith"}, {"code": "10101011100011000000001111", "data": "South"}, {"code": "10101011100011000000010000", "data": "Sport"}, {"code": "10101011100011000000010001", "data": "Star"}, {"code": "10101011100011000000010010", "data": "Street"}, {"code": "10101011100011000000010011", "data": "TV"}, {"code": "10101011100011000000010100", "data": "Teaching"}, {"code": "10101011100011000000010101", "data": "The"}, {"code": "10101011100011000000010110", "data": "Today"}, {"code": "10101011100011000000010111", "data": "Tonight"}, {"code": "10101011100011000000011000", "data": "Weather"}, {"code": "10101011100011000000011001", "data": "Western"}, {"code": "10101011100011000000011010", "data": "Westminster"}, {"code": "10101011100011000000011011", "data": "William"}, {"code": "10101011100011000000011100", "data": "With"}, {"code": "10101011100011000000011101", "data": "World"}, {"code": "10101011100011000000011110", "data": "about"}, {"code": "10101011100011000000011111", "data": "action-packed"}, {"code": "10101011100011000000100000", "data": "adventure"}, {"code": "10101011100011000000100001", "data": "afternoon"}, {"code": "10101011100011000000100010", "data": "alert"}, {"code": "10101011100011000000100011", "data": "all-star cast"}, {"code": "10101011100011000000100100", "data": "and"}, {"code": "10101011100011000000100101", "data": "anywhere"}, {"code": "10101011100011000000100110", "data": "audience"}, {"code": "10101011100011000000100111", "data": "based"}, {"code": "10101011100011000000101000", "data": "book"}, {"code": "10101011100011000000101001", "data": "business"}, {"code": "10101011100011000000101010", "data": "but"}, {"code": "10101011100011000000101011", "data": "celebrity"}, {"code": "10101011100011000000101100", "data": "chance"}, {"code": "10101011100011000000101101", "data": "chat"}, {"code": "10101011100011000000101110", "data": "child"}, {"code": "10101011100011000000101111", "data": "classic"}, {"code": "10101011100011000000110000", "data": "consumer"}, {"code": "10101011100011000000110001", "data": "contestants"}, {"code": "10101011100011000000110010", "data": "continues"}, {"code": "10101011100011000000110011", "data": "controversial"}, {"code": "10101011100011000000110100", "data": "dealer"}, {"code": "10101011100011000000110101", "data": "deliver"}, {"code": "10101011100011000000110110", "data": "discuss"}, {"code": "10101011100011000000110111", "data": "document"}, {"code": "10101011100011000000111000", "data": "drama"}, {"code": "10101011100011000000111001", "data": "edition"}, {"code": "10101011100011000000111010", "data": "education"}, {"code": "10101011100011000000111011", "data": "events"}, {"code": "10101011100011000000111100", "data": "every"}, {"code": "10101011100011000000111101", "data": "excellent"}, {"code": "10101011100011000000111110", "data": "eyed"}, {"code": "10101011100011000000111111", "data": "family"}, {"code": "10101011100011000001000000", "data": "famous"}, {"code": "10101011100011000001000001", "data": "featur"}, {"code": "10101011100011000001000010", "data": "film"}, {"code": "10101011100011000001000011", "data": "football"}, {"code": "10101011100011000001000100", "data": "for"}, {"code": "10101011100011000001000101", "data": "from"}, {"code": "10101011100011000001000110", "data": "general knowledge"}, {"code": "10101011100011000001000111", "data": "get"}, {"code": "10101011100011000001001000", "data": "guest"}, {"code": "10101011100011000001001001", "data": "guests"}, {"code": "10101011100011000001001010", "data": "has"}, {"code": "10101011100011000001001011", "data": "have"}, {"code": "10101011100011000001001100", "data": "headline"}, {"code": "10101011100011000001001101", "data": "her"}, {"code": "10101011100011000001001110", "data": "his"}, {"code": "10101011100011000001001111", "data": "home and abroad"}, {"code": "10101011100011000001010000", "data": "host"}, {"code": "10101011100011000001010001", "data": "how"}, {"code": "10101011100011000001010010", "data": "in"}, {"code": "10101011100011000001010011", "data": "including"}, {"code": "10101011100011000001010100", "data": "international"}, {"code": "10101011100011000001010101", "data": "interview"}, {"code": "10101011100011000001010110", "data": "introduce"}, {"code": "10101011100011000001010111", "data": "investigat"}, {"code": "10101011100011000001011000", "data": "invites"}, {"code": "10101011100011000001011001", "data": "issue"}, {"code": "10101011100011000001011010", "data": "knowledge"}, {"code": "10101011100011000001011011", "data": "life"}, {"code": "10101011100011000001011100", "data": "live"}, {"code": "10101011100011000001011101", "data": "look"}, {"code": "10101011100011000001011110", "data": "magazine"}, {"code": "10101011100011000001011111", "data": "meets "}, {"code": "10101011100011000001100000", "data": "morning"}, {"code": "10101011100011000001100001", "data": "morning magazine"}, {"code": "10101011100011000001100010", "data": "music"}, {"code": "10101011100011000001100011", "data": "near"}, {"code": "10101011100011000001100100", "data": "network"}, {"code": "10101011100011000001100101", "data": "new"}, {"code": "10101011100011000001100110", "data": "new series"}, {"code": "10101011100011000001100111", "data": "night"}, {"code": "10101011100011000001101000", "data": "of"}, {"code": "10101011100011000001101001", "data": "on"}, {"code": "10101011100011000001101010", "data": "onight"}, {"code": "10101011100011000001101011", "data": "out"}, {"code": "10101011100011000001101100", "data": "over"}, {"code": "10101011100011000001101101", "data": "part"}, {"code": "10101011100011000001101110", "data": "people"}, {"code": "10101011100011000001101111", "data": "phone"}, {"code": "10101011100011000001110000", "data": "poli"}, {"code": "10101011100011000001110001", "data": "police"}, {"code": "10101011100011000001110010", "data": "political chat show"}, {"code": "10101011100011000001110011", "data": "popular"}, {"code": "10101011100011000001110100", "data": "presented by "}, {"code": "10101011100011000001110101", "data": "programm"}, {"code": "10101011100011000001110110", "data": "quiz"}, {"code": "10101011100011000001110111", "data": "reconstruction"}, {"code": "10101011100011000001111000", "data": "report"}, {"code": "10101011100011000001111001", "data": "review"}, {"code": "10101011100011000001111010", "data": "school"}, {"code": "10101011100011000001111011", "data": "series"}, {"code": "10101011100011000001111100", "data": "short "}, {"code": "10101011100011000001111101", "data": "show"}, {"code": "10101011100011000001111110", "data": "some"}, {"code": "10101011100011000001111111", "data": "starring"}, {"code": "10101011100011000010000000", "data": "stars"}, {"code": "10101011100011000010000001", "data": "stories"}, {"code": "10101011100011000010000010", "data": "story"}, {"code": "10101011100011000010000011", "data": "studio"}, {"code": "10101011100011000010000100", "data": "surprise"}, {"code": "10101011100011000010000101", "data": "teller"}, {"code": "10101011100011000010000110", "data": "that"}, {"code": "10101011100011000010000111", "data": "the"}, {"code": "10101011100011000010001000", "data": "their"}, {"code": "10101011100011000010001001", "data": "them"}, {"code": "10101011100011000010001010", "data": "they"}, {"code": "10101011100011000010001011", "data": "this"}, {"code": "10101011100011000010001100", "data": "through"}, {"code": "10101011100011000010001101", "data": "to"}, {"code": "10101011100011000010001110", "data": "top"}, {"code": "10101011100011000010001111", "data": "trans"}, {"code": "10101011100011000010010000", "data": "under"}, {"code": "10101011100011000010010001", "data": "up"}, {"code": "10101011100011000010010010", "data": "very"}, {"code": "10101011100011000010010011", "data": "video"}, {"code": "10101011100011000010010100", "data": "view"}, {"code": "10101011100011000010010101", "data": "vintage"}, {"code": "10101011100011000010010110", "data": "visit"}, {"code": "10101011100011000010010111", "data": "was"}, {"code": "10101011100011000010011000", "data": "way"}, {"code": "10101011100011000010011001", "data": "week"}, {"code": "10101011100011000010011010", "data": "well"}, {"code": "10101011100011000010011011", "data": "what"}, {"code": "10101011100011000010011100", "data": "when"}, {"code": "10101011100011000010011101", "data": "which"}, {"code": "10101011100011000010011110", "data": "while"}, {"code": "10101011100011000010011111", "data": "who"}, {"code": "10101011100011000010100000", "data": "will"}, {"code": "10101011100011000010100001", "data": "win"}, {"code": "10101011100011000010100010", "data": "with"}, {"code": "10101011100011000010100011", "data": "words"}, {"code": "10101011100011000010100100", "data": "world"}, {"code": "10101011100011000010100101", "data": "written"}, {"code": "100010001100", "data": "year"}, {"code": "10110011010", "data": "you"}] diff --git a/data/epggrab/opentv/prov/skyuk b/data/epggrab/opentv/prov/skyuk new file mode 100644 index 00000000..49e51ac3 --- /dev/null +++ b/data/epggrab/opentv/prov/skyuk @@ -0,0 +1,16 @@ +{ + "name": "Sky UK", + "dict": "skyeng", + "nid": 2, + "tsid": 2004, + "sid": 4152, + "channel" : [ + 17 + ], + "title": [ + 48, 49, 50, 51, 52, 53, 54, 55 + ], + "summary": [ + 64, 65, 66, 67, 68, 69, 70, 71 + ] +} diff --git a/src/dvb/dvb.h b/src/dvb/dvb.h index 80ff4556..4e4e6825 100644 --- a/src/dvb/dvb.h +++ b/src/dvb/dvb.h @@ -207,6 +207,46 @@ typedef struct th_dvb_adapter { } th_dvb_adapter_t; +/** + * DVB table + */ +typedef struct th_dvb_table { + /** + * Flags, must never be changed after creation. + * We inspect it without holding global_lock + */ + int tdt_flags; + + /** + * Cycle queue + * Tables that did not get a fd or filter in hardware will end up here + * waiting for any other table to be received so it can reuse that fd. + * Only linked if fd == -1 + */ + TAILQ_ENTRY(th_dvb_table) tdt_pending_link; + + /** + * File descriptor for filter + */ + int tdt_fd; + + LIST_ENTRY(th_dvb_table) tdt_link; + + char *tdt_name; + + void *tdt_opaque; + int (*tdt_callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, + uint8_t tableid, void *opaque); + + + int tdt_count; + int tdt_pid; + + struct dmx_sct_filter_params *tdt_fparams; + + int tdt_id; + +} th_dvb_table_t; extern struct th_dvb_adapter_queue dvb_adapters; @@ -331,6 +371,17 @@ void dvb_table_add_default(th_dvb_mux_instance_t *tdmi); void dvb_table_flush_all(th_dvb_mux_instance_t *tdmi); +struct dmx_sct_filter_params *dvb_fparams_alloc(void); +void +tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams, + int (*callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, + uint8_t tableid, void *opaque), void *opaque, + const char *name, int flags, int pid, th_dvb_table_t *tdt); + +#define TDT_CRC 0x1 +#define TDT_QUICKREQ 0x2 +#define TDT_CA 0x4 + /** * Satellite configuration */ diff --git a/src/dvb/dvb_fe.c b/src/dvb/dvb_fe.c index 9e7b7890..64402bab 100644 --- a/src/dvb/dvb_fe.c +++ b/src/dvb/dvb_fe.c @@ -40,6 +40,8 @@ #include "notify.h" #include "dvr/dvr.h" +#include "epggrab.h" + /** * Return uncorrected block (since last read) * @@ -506,7 +508,9 @@ dvb_fe_tune(th_dvb_mux_instance_t *tdmi, const char *reason) gtimer_arm(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 1); + dvb_table_add_default(tdmi); + epggrab_tune(tdmi); dvb_adapter_notify(tda); return 0; diff --git a/src/dvb/dvb_tables.c b/src/dvb/dvb_tables.c index c9e7ad43..89291f1d 100644 --- a/src/dvb/dvb_tables.c +++ b/src/dvb/dvb_tables.c @@ -41,70 +41,20 @@ #include "psi.h" #include "notify.h" #include "cwc.h" -#include "epggrab/eit.h" - -#define TDT_CRC 0x1 -#define TDT_QUICKREQ 0x2 -#define TDT_CA 0x4 static void dvb_table_add_pmt(th_dvb_mux_instance_t *tdmi, int pmt_pid); static int tdt_id_tally; -/** - * - */ -typedef struct th_dvb_table { - /** - * Flags, must never be changed after creation. - * We inspect it without holding global_lock - */ - int tdt_flags; - - /** - * Cycle queue - * Tables that did not get a fd or filter in hardware will end up here - * waiting for any other table to be received so it can reuse that fd. - * Only linked if fd == -1 - */ - TAILQ_ENTRY(th_dvb_table) tdt_pending_link; - - /** - * File descriptor for filter - */ - int tdt_fd; - - LIST_ENTRY(th_dvb_table) tdt_link; - - char *tdt_name; - - void *tdt_opaque; - int (*tdt_callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, - uint8_t tableid, void *opaque); - - - int tdt_count; - int tdt_pid; - - struct dmx_sct_filter_params *tdt_fparams; - - int tdt_id; - -} th_dvb_table_t; - - - - /** * Helper for preparing a section filter parameter struct */ -static struct dmx_sct_filter_params * +struct dmx_sct_filter_params * dvb_fparams_alloc(void) { return calloc(1, sizeof(struct dmx_sct_filter_params)); } - /** * */ @@ -324,7 +274,7 @@ dvb_tdt_destroy(th_dvb_adapter_t *tda, th_dvb_mux_instance_t *tdmi, /** * Add a new DVB table */ -static void +void tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams, int (*callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tableid, void *opaque), void *opaque, @@ -332,8 +282,13 @@ tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams, { th_dvb_table_t *t; + // Allow multiple entries per PID, but only one per callback/opaque instance + // TODO: this could mean reading the same data multiple times, and not + // sure how well this will work! I know Andreas has some thoughts on + // this LIST_FOREACH(t, &tdmi->tdmi_tables, tdt_link) { - if(pid == t->tdt_pid) { + if(pid == t->tdt_pid && + t->tdt_callback == callback && t->tdt_opaque == opaque) { free(tdt); free(fparams); return; @@ -364,95 +319,6 @@ tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams, tdt_open_fd(tdmi, tdt); } - -/** - * DVB Descriptor; Short Event - */ -static int -dvb_desc_short_event(uint8_t *ptr, int len, - char *title, size_t titlelen, - char *desc, size_t desclen, - char *dvb_default_charset) -{ - int r; - - if(len < 4) - return -1; - ptr += 3; len -= 3; - - if((r = dvb_get_string_with_len(title, titlelen, ptr, len, dvb_default_charset)) < 0) - return -1; - ptr += r; len -= r; - - if((r = dvb_get_string_with_len(desc, desclen, ptr, len, dvb_default_charset)) < 0) - return -1; - - return 0; -} - -/** - * DVB Descriptor; Extended Event - */ -static int -dvb_desc_extended_event(uint8_t *ptr, int len, - char *desc, size_t desclen, - char *item, size_t itemlen, - char *text, size_t textlen, - char *dvb_default_charset) -{ - int count = ptr[4], r; - uint8_t *localptr = ptr + 5, *items = localptr; - int locallen = len - 5; - - /* terminate buffers */ - desc[0] = '\0'; item[0] = '\0'; text[0] = '\0'; - - while (items < (localptr + count)) - { - /* this only makes sense if we have 2 or more character left in buffer */ - if ((desclen - strlen(desc)) > 2) - { - /* get description -> append to desc if space left */ - if (desc[0] != '\0') - strncat(desc, "\n", 1); - if((r = dvb_get_string_with_len(desc + strlen(desc), - desclen - strlen(desc), - items, (localptr + count) - items, - dvb_default_charset)) < 0) - return -1; - } - - items += 1 + items[0]; - - /* this only makes sense if we have 2 or more character left in buffer */ - if ((itemlen - strlen(item)) > 2) - { - /* get item -> append to item if space left */ - if (item[0] != '\0') - strncat(item, "\n", 1); - if((r = dvb_get_string_with_len(item + strlen(item), - itemlen - strlen(item), - items, (localptr + count) - items, - dvb_default_charset)) < 0) - return -1; - } - - /* go to next item */ - items += 1 + items[0]; - } - - localptr += count; - locallen -= count; - count = localptr[0]; - - /* get text */ - if((r = dvb_get_string_with_len(text, textlen, localptr, locallen, dvb_default_charset)) < 0) - return -1; - - return 0; -} - - /** * DVB Descriptor; Service */ @@ -481,139 +347,6 @@ dvb_desc_service(uint8_t *ptr, int len, uint8_t *typep, return 0; } - -/** - * DVB EIT (Event Information Table) - */ -static int -dvb_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, - uint8_t tableid, void *opaque) -{ - service_t *t; - channel_t *ch; - th_dvb_adapter_t *tda = tdmi->tdmi_adapter; - - uint16_t serviceid; - uint16_t transport_stream_id; - - uint16_t event_id; - time_t start_time, stop_time; - - int ok; - int duration; - int dllen; - uint8_t dtag, dlen; - - char title[256]; - char desc[5000]; - char extdesc[5000]; - char extitem[5000]; - char exttext[5000]; - uint8_t genre[10]; // max 10 genres - int genre_idx = 0; - - lock_assert(&global_lock); - - // printf("EIT!, tid = %x\n", tableid); - - if(tableid < 0x4e || tableid > 0x6f || len < 11) - return -1; - - serviceid = ptr[0] << 8 | ptr[1]; - // version = ptr[2] >> 1 & 0x1f; - // section_number = ptr[3]; - // last_section_number = ptr[4]; - transport_stream_id = ptr[5] << 8 | ptr[6]; - // original_network_id = ptr[7] << 8 | ptr[8]; - // segment_last_section_number = ptr[9]; - // last_table_id = ptr[10]; - - if((ptr[2] & 1) == 0) { - /* current_next_indicator == next, skip this */ - return -1; - } - - len -= 11; - ptr += 11; - - /* Search all muxes on adapter */ - LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) - if(tdmi->tdmi_transport_stream_id == transport_stream_id) - break; - - if(tdmi == NULL) - return -1; - - t = dvb_transport_find(tdmi, serviceid, 0, NULL); - if(t == NULL || !t->s_enabled || (ch = t->s_ch) == NULL) - return 0; - - if(!t->s_dvb_eit_enable) - return 0; - - while(len >= 12) { - ok = 1; - event_id = ptr[0] << 8 | ptr[1]; - start_time = dvb_convert_date(&ptr[2]); - duration = bcdtoint(ptr[7] & 0xff) * 3600 + - bcdtoint(ptr[8] & 0xff) * 60 + - bcdtoint(ptr[9] & 0xff); - dllen = ((ptr[10] & 0x0f) << 8) | ptr[11]; - - len -= 12; - ptr += 12; - - if(dllen > len) break; - stop_time = start_time + duration; - - *title = 0; - *desc = 0; - while(dllen > 0) { - dtag = ptr[0]; - dlen = ptr[1]; - - len -= 2; ptr += 2; dllen -= 2; - - if(dlen > len) break; - - switch(dtag) { - case DVB_DESC_SHORT_EVENT: - if(dvb_desc_short_event(ptr, dlen, - title, sizeof(title), - desc, sizeof(desc), - t->s_dvb_default_charset)) ok = 0; - break; - - case DVB_DESC_CONTENT: - if(dlen >= 2) { - if (genre_idx < 10) - genre[genre_idx++] = (*ptr); - } - break; - case DVB_DESC_EXT_EVENT: - if(dvb_desc_extended_event(ptr, dlen, - extdesc, sizeof(extdesc), - extitem, sizeof(extitem), - exttext, sizeof(exttext), - t->s_dvb_default_charset)) ok = 0; - break; - default: - break; - } - - len -= dlen; ptr += dlen; dllen -= dlen; - } - - /* Pass to EIT handler */ - if (ok) - eit_callback(ch, event_id, start_time, stop_time, - title, desc, extitem, extdesc, exttext, - genre, genre_idx); - } - return 0; -} - - /** * DVB SDT (Service Description Table) */ @@ -1286,12 +1019,6 @@ dvb_table_add_default_dvb(th_dvb_mux_instance_t *tdmi) fp->filter.mask[0] = 0xff; tdt_add(tdmi, fp, dvb_sdt_callback, NULL, "sdt", TDT_QUICKREQ | TDT_CRC, 0x11, NULL); - - /* Event Information table */ - - fp = dvb_fparams_alloc(); - tdt_add(tdmi, fp, dvb_eit_callback, NULL, "eit", - TDT_CRC, 0x12, NULL); } diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index 541c29d6..552b132a 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -1074,6 +1074,7 @@ dvr_config_create(const char *name) cfg->dvr_sl_channel_lock = 1; // channel locked cfg->dvr_sl_time_lock = 0; // time slot (approx) locked cfg->dvr_sl_more_recent = 1; // Only record more reason episodes + cfg->dvr_sl_quality_lock = 1; // Don't attempt to ajust quality /* dup detect */ cfg->dvr_dup_detect_episode = 1; // detect dup episodes diff --git a/src/epg.c b/src/epg.c index 31a9c32b..78c6847f 100644 --- a/src/epg.c +++ b/src/epg.c @@ -1318,6 +1318,15 @@ epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id, channel_t *ch ) return (epg_broadcast_t*)_epg_object_find_by_id(id); } +epg_broadcast_t *epg_broadcast_find_by_eid ( int eid, channel_t *ch ) +{ + epg_broadcast_t *e; + RB_FOREACH(e, &ch->ch_epg_schedule, sched_link) { + if (e->dvb_eid == eid) return e; + } + return NULL; +} + int epg_broadcast_set_episode ( epg_broadcast_t *broadcast, epg_episode_t *episode ) { diff --git a/src/epg.h b/src/epg.h index a5ea1367..defa092d 100644 --- a/src/epg.h +++ b/src/epg.h @@ -283,6 +283,7 @@ struct epg_broadcast { epg_object_t; ///< Parent object + int dvb_eid; ///< DVB Event ID time_t start; ///< Start time time_t stop; ///< End time @@ -312,6 +313,7 @@ struct epg_broadcast epg_broadcast_t *epg_broadcast_find_by_time ( struct channel *ch, time_t start, time_t stop, int create, int *save ); epg_broadcast_t *epg_broadcast_find_by_id ( uint64_t id, struct channel *ch ); +epg_broadcast_t *epg_broadcast_find_by_eid ( int eid, struct channel *ch ); /* Mutators */ int epg_broadcast_set_episode ( epg_broadcast_t *b, epg_episode_t *e ) diff --git a/src/epggrab.c b/src/epggrab.c index 2edb841e..c36b7bbb 100644 --- a/src/epggrab.c +++ b/src/epggrab.c @@ -16,6 +16,7 @@ #include "epggrab/eit.h" #include "epggrab/xmltv.h" #include "epggrab/pyepg.h" +#include "epggrab/opentv.h" #include "channels.h" #include "spawn.h" #include "htsmsg_xml.h" @@ -28,7 +29,6 @@ pthread_mutex_t epggrab_mutex; pthread_cond_t epggrab_cond; /* Config */ -uint32_t epggrab_eitenabled; uint32_t epggrab_interval; epggrab_module_t* epggrab_module; epggrab_module_list_t epggrab_modules; @@ -663,7 +663,6 @@ static void _epggrab_load ( void ) /* Process */ if (m) { - htsmsg_get_u32(m, "eit", &epggrab_eitenabled); if (!htsmsg_get_u32(m, old ? "grab-interval" : "interval", &epggrab_interval)) if (old) epggrab_interval *= 3600; htsmsg_get_u32(m, "grab-enabled", &enabled); @@ -712,6 +711,7 @@ static void _epggrab_load ( void ) eit_load(); xmltv_load(); pyepg_load(); + opentv_load(); } void epggrab_save ( void ) @@ -725,7 +725,6 @@ void epggrab_save ( void ) /* Save */ m = htsmsg_create_map(); - htsmsg_add_u32(m, "eitenabled", epggrab_eitenabled); htsmsg_add_u32(m, "interval", epggrab_interval); if ( epggrab_module ) htsmsg_add_str(m, "module", epggrab_module->id); @@ -741,17 +740,6 @@ void epggrab_save ( void ) htsmsg_destroy(m); } -int epggrab_set_eitenabled ( uint32_t eitenabled ) -{ - // TODO: could use module variable - int save = 0; - if ( epggrab_eitenabled != eitenabled ) { - save = 1; - epggrab_eitenabled = eitenabled; - } - return save; -} - int epggrab_set_interval ( uint32_t interval ) { int save = 0; @@ -811,7 +799,6 @@ int epggrab_enable_module_by_id ( const char *id, uint8_t e ) void epggrab_init ( void ) { /* Defaults */ - epggrab_eitenabled = 1; // on air grab enabled epggrab_interval = 12 * 3600; // hours epggrab_module = NULL; // disabled @@ -819,6 +806,7 @@ void epggrab_init ( void ) eit_init(&epggrab_modules); xmltv_init(&epggrab_modules); pyepg_init(&epggrab_modules); + opentv_init(&epggrab_modules); /* Load config */ _epggrab_load(); @@ -855,6 +843,14 @@ void epggrab_channel_mod ( channel_t *ch ) } } +void epggrab_tune ( th_dvb_mux_instance_t *tdmi ) +{ + epggrab_module_t *m; + LIST_FOREACH(m, &epggrab_modules, link) { + if (m->tune) m->tune(m, tdmi); + } +} + epggrab_module_t* epggrab_module_find_by_id ( const char *id ) { epggrab_module_t *m; diff --git a/src/epggrab.h b/src/epggrab.h index 7a6aba19..88e3bb15 100644 --- a/src/epggrab.h +++ b/src/epggrab.h @@ -3,6 +3,7 @@ #include #include "channels.h" +#include "dvb/dvb.h" typedef struct epggrab_module epggrab_module_t; @@ -85,9 +86,9 @@ void epggrab_channel_link ( epggrab_channel_t *ch ); /* * Grabber flags */ -#define EPGGRAB_MODULE_SIMPLE 0x01 -#define EPGGRAB_MODULE_EXTERNAL 0x02 -#define EPGGRAB_MODULE_SPECIAL 0x04 +#define EPGGRAB_MODULE_INTERNAL 0x01 ///< IP based internally run +#define EPGGRAB_MODULE_EXTERNAL 0x02 ///< IP based externally run +#define EPGGRAB_MODULE_OTA 0x04 ///< DVB OTA EPGs /* * Grabber base class @@ -117,6 +118,9 @@ struct epggrab_module void (*ch_add) ( epggrab_module_t *m, channel_t *ch ); void (*ch_rem) ( epggrab_module_t *m, channel_t *ch ); void (*ch_mod) ( epggrab_module_t *m, channel_t *ch ); + + /* Transponder tuning */ + void (*tune) ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi ); }; /* @@ -159,14 +163,12 @@ htsmsg_t* epggrab_module_list ( void ); * Configuration */ extern pthread_mutex_t epggrab_mutex; -extern uint32_t epggrab_eitenabled; extern uint32_t epggrab_interval; extern epggrab_module_t* epggrab_module; /* * Update */ -int epggrab_set_eitenabled ( uint32_t eitenabled ); int epggrab_set_interval ( uint32_t interval ); int epggrab_set_module ( epggrab_module_t *module ); int epggrab_set_module_by_id ( const char *id ); @@ -187,4 +189,9 @@ void epggrab_channel_add ( struct channel *ch ); void epggrab_channel_rem ( struct channel *ch ); void epggrab_channel_mod ( struct channel *ch ); +/* + * Transport handling + */ +void epggrab_tune ( th_dvb_mux_instance_t *tdmi ); + #endif /* __EPGGRAB_H__ */ diff --git a/src/epggrab/eit.c b/src/epggrab/eit.c index 3670eacd..0d90c2e4 100644 --- a/src/epggrab/eit.c +++ b/src/epggrab/eit.c @@ -19,11 +19,16 @@ #include #include "tvheadend.h" #include "channels.h" +#include "dvb/dvb.h" +#include "dvb/dvb_support.h" +#include "service.h" #include "epg.h" #include "epggrab/eit.h" +static epggrab_module_t _eit_mod; + /* ************************************************************************ - * Module Setup + * Processing * ***********************************************************************/ static const char * @@ -34,8 +39,7 @@ longest_string ( const char *a, const char *b ) if (strlen(a) - strlen(b) >= 0) return a; } -// called from dvb_tables.c -void eit_callback ( channel_t *ch, int id, time_t start, time_t stop, +static void eit_callback ( channel_t *ch, int id, time_t start, time_t stop, const char *title, const char *desc, const char *extitem, const char *extdesc, const char *exttext, @@ -51,9 +55,6 @@ void eit_callback ( channel_t *ch, int id, time_t start, time_t stop, if (!ch || !ch->ch_name || !ch->ch_name[0]) return; if (!title) return; - /* Disabled? */ - if (!epggrab_eitenabled) return; - /* Find broadcast */ ebc = epg_broadcast_find_by_time(ch, start, stop, 1, &save); if (!ebc) return; @@ -93,17 +94,244 @@ void eit_callback ( channel_t *ch, int id, time_t start, time_t stop, free(uri); } + +/** + * DVB Descriptor; Short Event + */ +static int +dvb_desc_short_event(uint8_t *ptr, int len, + char *title, size_t titlelen, + char *desc, size_t desclen, + char *dvb_default_charset) +{ + int r; + + if(len < 4) + return -1; + ptr += 3; len -= 3; + + if((r = dvb_get_string_with_len(title, titlelen, ptr, len, dvb_default_charset)) < 0) + return -1; + ptr += r; len -= r; + + if((r = dvb_get_string_with_len(desc, desclen, ptr, len, dvb_default_charset)) < 0) + return -1; + + return 0; +} + +/** + * DVB Descriptor; Extended Event + */ +static int +dvb_desc_extended_event(uint8_t *ptr, int len, + char *desc, size_t desclen, + char *item, size_t itemlen, + char *text, size_t textlen, + char *dvb_default_charset) +{ + int count = ptr[4], r; + uint8_t *localptr = ptr + 5, *items = localptr; + int locallen = len - 5; + + /* terminate buffers */ + desc[0] = '\0'; item[0] = '\0'; text[0] = '\0'; + + while (items < (localptr + count)) + { + /* this only makes sense if we have 2 or more character left in buffer */ + if ((desclen - strlen(desc)) > 2) + { + /* get description -> append to desc if space left */ + if (desc[0] != '\0') + strncat(desc, "\n", 1); + if((r = dvb_get_string_with_len(desc + strlen(desc), + desclen - strlen(desc), + items, (localptr + count) - items, + dvb_default_charset)) < 0) + return -1; + } + + items += 1 + items[0]; + + /* this only makes sense if we have 2 or more character left in buffer */ + if ((itemlen - strlen(item)) > 2) + { + /* get item -> append to item if space left */ + if (item[0] != '\0') + strncat(item, "\n", 1); + if((r = dvb_get_string_with_len(item + strlen(item), + itemlen - strlen(item), + items, (localptr + count) - items, + dvb_default_charset)) < 0) + return -1; + } + + /* go to next item */ + items += 1 + items[0]; + } + + localptr += count; + locallen -= count; + count = localptr[0]; + + /* get text */ + if((r = dvb_get_string_with_len(text, textlen, localptr, locallen, dvb_default_charset)) < 0) + return -1; + + return 0; +} + + +/** + * DVB EIT (Event Information Table) + */ +static int +_eit_callback(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len, + uint8_t tableid, void *opaque) +{ + service_t *t; + channel_t *ch; + th_dvb_adapter_t *tda = tdmi->tdmi_adapter; + + uint16_t serviceid; + uint16_t transport_stream_id; + + uint16_t event_id; + time_t start_time, stop_time; + + int ok; + int duration; + int dllen; + uint8_t dtag, dlen; + + char title[256]; + char desc[5000]; + char extdesc[5000]; + char extitem[5000]; + char exttext[5000]; + uint8_t genre[10]; // max 10 genres + int genre_idx = 0; + + /* Global disable */ + if (!_eit_mod.enabled) return 0; + + lock_assert(&global_lock); + + // printf("EIT!, tid = %x\n", tableid); + + if(tableid < 0x4e || tableid > 0x6f || len < 11) + return -1; + + serviceid = ptr[0] << 8 | ptr[1]; + // version = ptr[2] >> 1 & 0x1f; + // section_number = ptr[3]; + // last_section_number = ptr[4]; + transport_stream_id = ptr[5] << 8 | ptr[6]; + // original_network_id = ptr[7] << 8 | ptr[8]; + // segment_last_section_number = ptr[9]; + // last_table_id = ptr[10]; + + if((ptr[2] & 1) == 0) { + /* current_next_indicator == next, skip this */ + return -1; + } + + len -= 11; + ptr += 11; + + /* Search all muxes on adapter */ + LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) + if(tdmi->tdmi_transport_stream_id == transport_stream_id) + break; + + if(tdmi == NULL) + return -1; + + t = dvb_transport_find(tdmi, serviceid, 0, NULL); + if(t == NULL || !t->s_enabled || (ch = t->s_ch) == NULL) + return 0; + + if(!t->s_dvb_eit_enable) + return 0; + + while(len >= 12) { + ok = 1; + event_id = ptr[0] << 8 | ptr[1]; + start_time = dvb_convert_date(&ptr[2]); + duration = bcdtoint(ptr[7] & 0xff) * 3600 + + bcdtoint(ptr[8] & 0xff) * 60 + + bcdtoint(ptr[9] & 0xff); + dllen = ((ptr[10] & 0x0f) << 8) | ptr[11]; + + len -= 12; + ptr += 12; + + if(dllen > len) break; + stop_time = start_time + duration; + + *title = 0; + *desc = 0; + while(dllen > 0) { + dtag = ptr[0]; + dlen = ptr[1]; + + len -= 2; ptr += 2; dllen -= 2; + + if(dlen > len) break; + + switch(dtag) { + case DVB_DESC_SHORT_EVENT: + if(dvb_desc_short_event(ptr, dlen, + title, sizeof(title), + desc, sizeof(desc), + t->s_dvb_default_charset)) ok = 0; + break; + + case DVB_DESC_CONTENT: + if(dlen >= 2) { + if (genre_idx < 10) + genre[genre_idx++] = (*ptr); + } + break; + case DVB_DESC_EXT_EVENT: + if(dvb_desc_extended_event(ptr, dlen, + extdesc, sizeof(extdesc), + extitem, sizeof(extitem), + exttext, sizeof(exttext), + t->s_dvb_default_charset)) ok = 0; + break; + default: + break; + } + + len -= dlen; ptr += dlen; dllen -= dlen; + } + + /* Pass to EIT handler */ + if (ok) + eit_callback(ch, event_id, start_time, stop_time, + title, desc, extitem, extdesc, exttext, + genre, genre_idx); + } + return 0; +} + /* ************************************************************************ * Module Setup * ***********************************************************************/ -static epggrab_module_t _eit_mod; +static void _eit_tune ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi ) +{ + tdt_add(tdmi, NULL, _eit_callback, NULL, "eit", TDT_CRC, 0x12, NULL); +} void eit_init ( epggrab_module_list_t *list ) { _eit_mod.id = strdup("eit"); _eit_mod.name = strdup("EIT: On-Air Grabber"); - *((uint8_t*)&_eit_mod.flags) = EPGGRAB_MODULE_SPECIAL; + _eit_mod.tune = _eit_tune; + *((uint8_t*)&_eit_mod.flags) = EPGGRAB_MODULE_OTA; LIST_INSERT_HEAD(list, &_eit_mod, link); // Note: this is mostly ignored anyway as EIT is treated as a special case! } diff --git a/src/epggrab/eit.h b/src/epggrab/eit.h index be58edeb..c278718b 100644 --- a/src/epggrab/eit.h +++ b/src/epggrab/eit.h @@ -24,10 +24,4 @@ void eit_init ( epggrab_module_list_t *list ); void eit_load ( void ); -void eit_callback ( struct channel *ch, int id, time_t start, time_t stop, - const char *title, const char *desc, - const char *extitem, const char *extdesc, - const char *exttext, - const uint8_t *genres, int genre_cnt ); - #endif diff --git a/src/epggrab/opentv.c b/src/epggrab/opentv.c new file mode 100644 index 00000000..a423289e --- /dev/null +++ b/src/epggrab/opentv.c @@ -0,0 +1,650 @@ +/* + * Electronic Program Guide - opentv epg grabber + * Copyright (C) 2012 Adam Sutton + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include "tvheadend.h" +#include "dvb/dvb.h" +#include "channels.h" +#include "huffman.h" +#include "epg.h" +#include "epggrab/opentv.h" +#include "subscriptions.h" +#include "streaming.h" +#include "service.h" +#include "htsmsg.h" +#include "settings.h" + +/* ************************************************************************ + * Configuration + * + * TODO: I think much of this may be moved to a generic lib, as some of + * the other OTA EPGs will have similar config reqs + * + * TODO: some of this is a bit overkill, for example the global tree of + * dicts/provs etc... they're only used to get things up and running + * ***********************************************************************/ + +/* Huffman dictionary */ +typedef struct opentv_dict +{ + char *id; + huffman_node_t *codes; + RB_ENTRY(opentv_dict) h_link; +} opentv_dict_t; + +/* Provider configuration */ +typedef struct opentv_prov +{ + char *id; + char *name; + RB_ENTRY(opentv_prov) h_link; + + int nid; + int tsid; + int sid; + int *channel; + int *title; + int *summary; + opentv_dict_t *dict; +} opentv_prov_t; + +/* Extension of epggrab module to include linked provider */ +typedef struct opentv_module +{ + epggrab_module_t ; ///< Base struct + opentv_prov_t *prov; ///< Associated provider config + pthread_mutex_t mutex; + pthread_cond_t cond; + time_t updated; +} opentv_module_t; + +/* + * Lists/Comparators + */ +RB_HEAD(opentv_dict_tree, opentv_dict); +RB_HEAD(opentv_prov_tree, opentv_prov); +struct opentv_dict_tree _opentv_dicts; +struct opentv_prov_tree _opentv_provs; + +static int _dict_cmp ( void *a, void *b ) +{ + return strcmp(((opentv_dict_t*)a)->id, ((opentv_dict_t*)b)->id); +} + +static int _prov_cmp ( void *a, void *b ) +{ + return strcmp(((opentv_prov_t*)a)->id, ((opentv_prov_t*)b)->id); +} + +static opentv_dict_t *_opentv_dict_find ( const char *id ) +{ + opentv_dict_t skel; + skel.id = (char*)id; + return RB_FIND(&_opentv_dicts, &skel, h_link, _dict_cmp); +} + +/* + * Configuration loading + */ + +static int* _pid_list_to_array ( htsmsg_t *m ) +{ + int i = 1; + int *ret; + htsmsg_field_t *f; + HTSMSG_FOREACH(f, m) + if (f->hmf_s64) i++; + ret = calloc(i, sizeof(int)); + i = 0; + HTSMSG_FOREACH(f, m) + if (f->hmf_s64) + ret[i++] = (int)f->hmf_s64; + return ret; +} + +static int _opentv_dict_load ( const char *id, htsmsg_t *m ) +{ + opentv_dict_t *dict = calloc(1, sizeof(opentv_dict_t)); + dict->id = (char*)id; + if (RB_INSERT_SORTED(&_opentv_dicts, dict, h_link, _dict_cmp)) { + tvhlog(LOG_WARNING, "opentv", "ignore duplicate dictionary %s", id); + free(dict); + return 0; + } else { + dict->codes = huffman_tree_build(m); + if (!dict->codes) { + RB_REMOVE(&_opentv_dicts, dict, h_link); + free(dict); + return -1; + } else { + dict->id = strdup(id); + return 1; + } + } +} + +static int _opentv_prov_load ( const char *id, htsmsg_t *m ) +{ + htsmsg_t *cl, *tl, *sl; + uint32_t tsid, sid, nid; + const char *str, *name; + opentv_dict_t *dict; + opentv_prov_t *prov; + + /* Check config */ + if (!(name = htsmsg_get_str(m, "name"))) return -1; + if (!(str = htsmsg_get_str(m, "dict"))) return -1; + if (!(dict = _opentv_dict_find(str))) return -1; + if (!(cl = htsmsg_get_list(m, "channel"))) return -1; + if (!(tl = htsmsg_get_list(m, "title"))) return -5; + if (!(sl = htsmsg_get_list(m, "summary"))) return -1; + if (htsmsg_get_u32(m, "nid", &nid)) return -1; + if (htsmsg_get_u32(m, "tsid", &tsid)) return -1; + if (htsmsg_get_u32(m, "sid", &sid)) return -1; + + prov = calloc(1, sizeof(opentv_prov_t)); + prov->id = (char*)id; + if (RB_INSERT_SORTED(&_opentv_provs, prov, h_link, _prov_cmp)) { + tvhlog(LOG_WARNING, "opentv", "ignore duplicate provider %s", id); + free(prov); + return 0; + } else { + prov->id = strdup(id); + prov->name = strdup(name); + prov->dict = dict; + prov->nid = nid; + prov->tsid = tsid; + prov->sid = sid; + prov->channel = _pid_list_to_array(cl); + prov->title = _pid_list_to_array(tl); + prov->summary = _pid_list_to_array(sl); + return 1; + } +} + +/* ************************************************************************ + * EPG Object wrappers + * ***********************************************************************/ + +static epggrab_channel_t *_opentv_find_epggrab_channel + ( opentv_module_t *mod, int cid, int create, int *save ) +{ + char chid[32]; + sprintf(chid, "%s-%d", mod->prov->id, cid); + return epggrab_module_channel_find((epggrab_module_t*)mod, chid, create, save); +} + +static epg_season_t *_opentv_find_season + ( opentv_module_t *mod, int cid, int slink ) +{ + int save = 0; + char uri[64]; + sprintf(uri, "%s-%d-%d", mod->prov->id, cid, slink); + return epg_season_find_by_uri(uri, 1, &save); +} + +static service_t *_opentv_find_service ( int tsid, int sid ) +{ + th_dvb_adapter_t *tda; + th_dvb_mux_instance_t *tdmi; + service_t *t = NULL; + TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link) { + LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) { + if (tdmi->tdmi_transport_stream_id != tsid) continue; + LIST_FOREACH(t, &tdmi->tdmi_transports, s_group_link) { + if (t->s_dvb_service_id == sid) return t; + } + } + } + return NULL; +} + +static channel_t *_opentv_find_channel ( int tsid, int sid ) +{ + service_t *t = _opentv_find_service(tsid, sid); + return t ? t->s_ch : NULL; +} + +/* ************************************************************************ + * OpenTV data processing + * ***********************************************************************/ + +#define MAX_LEN_TITLE 1024 +#define MAX_LEN_SUMMARY 1024 +#define MAX_LEN_DESC 2048 + +typedef struct opentv_event +{ + int eid; + int start; + int duration; + char title[MAX_LEN_TITLE]; + char summary[MAX_LEN_SUMMARY]; + char description[MAX_LEN_DESC]; + int serieslink; + uint8_t cat; +} opentv_event_t; + +/* Parse huffman encoded string */ +static const char *_parse_string + ( opentv_prov_t *prov, uint8_t *buf, int len, char *outb, int outl ) +{ + return huffman_decode(prov->dict->codes, buf, len, 0x20, outb, outl); +} + +/* Parse a specific record */ +static int _opentv_parse_event_record + ( opentv_prov_t *prov, opentv_event_t *ev, uint8_t *buf, int len ) +{ + uint8_t rtag = buf[0]; + uint8_t rlen = buf[1]; + if (rlen+2 <= len) { + switch (rtag) { + case 0xb5: // title + ev->start = (((int)buf[2] << 9) | (buf[3] << 1)); + ev->duration = (((int)buf[4] << 9) | (buf[5] << 1)); + ev->cat = buf[6]; + _parse_string(prov, buf+9, rlen-7, ev->title, MAX_LEN_TITLE); + break; + case 0xb9: // summary + _parse_string(prov, buf+2, rlen, ev->summary, MAX_LEN_SUMMARY); + break; + case 0xbb: // description + _parse_string(prov, buf+2, rlen, ev->description, MAX_LEN_DESC); + break; + case 0xc1: // series link + ev->serieslink = ((int)buf[2] << 8) | buf[3]; + break; + default: + break; + } + } + return rlen + 2; +} + +/* Parse a specific event */ +static int _opentv_parse_event + ( opentv_prov_t *prov, opentv_event_t *ev, uint8_t *buf, int len ) +{ + int slen = ((int)buf[2] & 0xf << 8) | buf[3]; + int i = 4; + ev->eid = ((uint16_t)buf[0] << 8) | buf[1]; + while (i < slen+4) { + i += _opentv_parse_event_record(prov, ev, buf+i, len-i); + } + return slen+4; +} + +/* Parse an event section */ +static int _opentv_parse_event_section + ( opentv_module_t *mod, uint8_t *buf, int len ) +{ + int i, cid, mjd, save = 0; + char *uri; + epggrab_channel_t *ec; + epg_broadcast_t *ebc; + epg_episode_t *ee; + epg_season_t *es; + opentv_event_t ev; + + /* Channel */ + cid = ((int)buf[0] << 8) | buf[1]; + if (!(ec = _opentv_find_epggrab_channel(mod, cid, 0, NULL))) return 0; + if (!ec->channel) return 0; + if (!*ec->channel->ch_name) return 0; // ignore unnamed channels + + /* Time (start/stop referenced to this) */ + mjd = ((int)buf[5] << 8) | buf[6]; + + /* Loop around event entries */ + i = 7; + while (i < len) { + memset(&ev, 0, sizeof(opentv_event_t)); + i += _opentv_parse_event(mod->prov, &ev, buf+i, len-i); + + /* Create/Find broadcast */ + if (ev.start && ev.duration) { + time_t start = ev.start + ((mjd - 40587) * 86400); + time_t stop = start + ev.duration; + ebc = epg_broadcast_find_by_time(ec->channel, start, stop, 1, &save); + } else { + ebc = epg_broadcast_find_by_eid(ev.eid, ec->channel); + } + + if (ebc) { + + /* Create/Find episode */ + if (*ev.description || *ev.summary) { + uri = md5sum(*ev.description ? ev.description : ev.summary); + ee = epg_episode_find_by_uri(uri, 1, &save); + free(uri); + } else if (ebc) { + ee = ebc->episode; + } + + /* DVB Event Id */ + // TODO: this causes serious problems for channels backed by multiple + // services where the ids don't match + if (ebc->dvb_eid != ev.eid) { + ebc->dvb_eid = ev.eid; + save = 1; + } + + /* Set episode data */ + if (ee) { + if (*ev.description) + save |= epg_episode_set_description(ee, ev.description); + if (*ev.summary) + save |= epg_episode_set_summary(ee, ev.summary); + if (*ev.title) + save |= epg_episode_set_title(ee, ev.title); + if (ev.cat) + save |= epg_episode_set_genre(ee, &ev.cat, 1); + + // Note: we don't overwrite an existing season, the reason for + // this is that seasons in opentv are channel specific but episodes + // are not (to allow episode matching) so this is a bit of bodge + // to make things play nice + if (ev.serieslink && !ee->season) { + es = _opentv_find_season(mod, cid, ev.serieslink); + if (es) save |= epg_episode_set_season(ee, es); + } + + save |= epg_broadcast_set_episode(ebc, ee); + } + } + } + + /* Update EPG */ + if (save) epg_updated(); + return 0; +} + +static int _opentv_event_callback + ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) +{ + return _opentv_parse_event_section((opentv_module_t*)p, buf, len); +} + +// TODO: this function is currently a bit of a mess +// TODO: bouqets are ignored, what useful info can we get from them? +static int _opentv_channel_callback + ( th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len, uint8_t tid, void *p ) +{ + opentv_module_t *mod = (opentv_module_t*)p; + epggrab_channel_t *ec; + int tsid, cid;//, cnum; + uint16_t sid; + int i, j, k, tdlen, dlen, dtag, tslen; + channel_t *ch; + if (tid != 0x4a) return 0; + i = 7 + ((((int)buf[5] & 0xf) << 8) | buf[6]); + tslen = (((int)buf[i] & 0xf) << 8) | buf[i+1]; + i += 2; + while (tslen > 0) { + tsid = ((int)buf[i] << 8) | buf[i+1]; + //nid = ((int)buf[i+2] << 8) | buf[i+3]; + tdlen = (((int)buf[i+4] & 0xf) << 8) | buf[i+5]; + j = i + 6; + i += (tdlen + 6); + tslen -= (tdlen + 6); + while (tdlen > 0) { + dtag = buf[j]; + dlen = buf[j+1]; + k = j + 2; + j += (dlen + 2); + tdlen -= (dlen + 2); + if (dtag == 0xb1) { + k += 2; + dlen -= 2; + while (dlen > 0) { + sid = ((int)buf[k] << 8) | buf[k+1]; + cid = ((int)buf[k+3] << 8) | buf[k+4]; + //cnum = ((int)buf[k+5] << 8) | buf[k+6]; + + /* Find the channel */ + ch = _opentv_find_channel(tsid, sid); + if (ch) { + int save = 0; + ec = _opentv_find_epggrab_channel(mod, cid, 1, &save); + if (save) { + // Note: could use set_sid() but not nec. + ec->channel = ch; + //TODO: must be configurable + //epggrab_channel_set_number(ec, cnum); + } + } + k += 9; + dlen -= 9; + } + } + } + } + return 0; +} + +/* ************************************************************************ + * Tuning Thread + * ***********************************************************************/ + +static void _opentv_stream ( void *p, streaming_message_t *sm ) +{ + // TODO: handle start? + // TODO: handle stop? +} + +// TODO: if channel not found we still wait +// TODO: make time periods configurable? +// TODO: dynamic detection of when to start/stop +static void* _opentv_thread ( void *p ) +{ + int err; + service_t *svc; + th_subscription_t *sub; + streaming_target_t stream; + time_t start; + struct timespec ts; + opentv_module_t *mod = (opentv_module_t*)p; + streaming_target_init(&stream, _opentv_stream, NULL, 0); + tvhlog(LOG_INFO, mod->id, "thread started\n"); + + do { + + /* Find channel and subscribe */ + tvhlog(LOG_DEBUG, mod->id, "grab begin %d %d", mod->prov->tsid, mod->prov->sid); + pthread_mutex_lock(&global_lock); + svc = _opentv_find_service(mod->prov->tsid, mod->prov->sid); + if (svc) { + sub = subscription_create_from_service(svc, mod->prov->id, + &stream, 0); + if (sub) subscription_change_weight(sub, 1); + } + else + sub = NULL; + pthread_mutex_unlock(&global_lock); + if (sub) tvhlog(LOG_DEBUG, mod->id, "subscription added"); + + /* Allow scanning */ + if (sub) { + time(&start); + ts.tv_nsec = 0; + pthread_mutex_lock(&mod->mutex); + while ( mod->enabled ) { + ts.tv_sec = start + 300; + err = pthread_cond_timedwait(&mod->cond, &mod->mutex, &ts); + if (err == ETIMEDOUT ) break; + } + pthread_mutex_unlock(&mod->mutex); + } + tvhlog(LOG_DEBUG, mod->id, "grab complete"); + + /* Terminate subscription */ + pthread_mutex_lock(&global_lock); + if (sub) subscription_unsubscribe(sub); + pthread_mutex_unlock(&global_lock); + + /* Wait */ + time(&mod->updated); + pthread_mutex_lock(&mod->mutex); + while ( mod->enabled ) { + ts.tv_sec = mod->updated + 3600; + err = pthread_cond_timedwait(&mod->cond, &mod->mutex, &ts); + if (err == ETIMEDOUT) break; + } + if (!mod->enabled) break; + pthread_mutex_unlock(&mod->mutex); + } while (1); + pthread_mutex_unlock(&mod->mutex); + + return NULL; +} + +/* ************************************************************************ + * Module Setup + * ***********************************************************************/ + +static epggrab_channel_tree_t _opentv_channels; + +static void _opentv_tune ( epggrab_module_t *m, th_dvb_mux_instance_t *tdmi ) +{ + int *t; + struct dmx_sct_filter_params *fp; + opentv_module_t *mod = (opentv_module_t*)m; + + /* Install tables */ + if (m->enabled && (mod->prov->tsid == tdmi->tdmi_transport_stream_id)) { + tvhlog(LOG_INFO, "opentv", "install provider %s tables", mod->prov->id); + + /* Channels */ + t = mod->prov->channel; + while (*t) { + fp = dvb_fparams_alloc(); + fp->filter.filter[0] = 0x4a; + fp->filter.mask[0] = 0xff; + // TODO: what about 0x46 (service description) + tdt_add(tdmi, fp, _opentv_channel_callback, mod, + m->id, TDT_CRC, *t++, NULL); + } + + /* Titles */ + t = mod->prov->title; + while (*t) { + fp = dvb_fparams_alloc(); + fp->filter.filter[0] = 0xa0; + fp->filter.mask[0] = 0xfc; + tdt_add(tdmi, fp, _opentv_event_callback, mod, + m->id, TDT_CRC, *t++, NULL); + } + + /* Summaries */ + t = mod->prov->summary; + while (*t) { + fp = dvb_fparams_alloc(); + fp->filter.filter[0] = 0xa8; + fp->filter.mask[0] = 0xfc; + tdt_add(tdmi, fp, _opentv_event_callback, mod, + m->id, TDT_CRC, *t++, NULL); + } + } +} + +static int _opentv_enable ( epggrab_module_t *m, uint8_t e ) +{ + int save = 0; + pthread_t t; + pthread_attr_t ta; + opentv_module_t *mod = (opentv_module_t*)m; + pthread_mutex_lock(&mod->mutex); + if (m->enabled != e) { + m->enabled = e; + if (e) { + pthread_attr_init(&ta); + pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED); + pthread_create(&t, &ta, _opentv_thread, mod); + } else + pthread_cond_signal(&mod->cond); + save = 1; + } + pthread_mutex_unlock(&mod->mutex); + return save; +} + +void opentv_init ( epggrab_module_list_t *list ) +{ + int r; + htsmsg_t *m, *e; + htsmsg_field_t *f; + opentv_prov_t *p; + opentv_module_t *mod; + char buf[100]; + + /* Load the dictionaries */ + if ((m = hts_settings_load("epggrab/opentv/dict"))) { + HTSMSG_FOREACH(f, m) { + if ((e = htsmsg_get_list(m, f->hmf_name))) { + if ((r = _opentv_dict_load(f->hmf_name, e))) { + if (r > 0) + tvhlog(LOG_INFO, "opentv", "dictionary %s loaded", f->hmf_name); + else + tvhlog(LOG_WARNING, "opentv", "dictionary %s failed", f->hmf_name); + } + } + } + htsmsg_destroy(m); + } + tvhlog(LOG_INFO, "opentv", "dictonaries loaded"); + + /* Load providers */ + if ((m = hts_settings_load("epggrab/opentv/prov"))) { + HTSMSG_FOREACH(f, m) { + if ((e = htsmsg_get_map_by_field(f))) { + if ((r = _opentv_prov_load(f->hmf_name, e))) { + if (r > 0) + tvhlog(LOG_INFO, "opentv", "provider %s loaded", f->hmf_name); + else + tvhlog(LOG_WARNING, "opentv", "provider %s failed", f->hmf_name); + } + } + } + htsmsg_destroy(m); + } + tvhlog(LOG_INFO, "opentv", "providers loaded"); + + /* Create modules */ + RB_FOREACH(p, &_opentv_provs, h_link) { + mod = calloc(1, sizeof(opentv_module_t)); + sprintf(buf, "opentv-%s", p->id); + mod->id = strdup(buf); + sprintf(buf, "OpenTV: %s", p->name); + mod->name = strdup(buf); + mod->enable = _opentv_enable; + mod->tune = _opentv_tune; + mod->channels = &_opentv_channels; + mod->prov = p; + *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_OTA; + LIST_INSERT_HEAD(list, ((epggrab_module_t*)mod), link); + } +} + +void opentv_load ( void ) +{ + // TODO: do we want to keep a list of channels stored? +} diff --git a/src/epggrab/opentv.h b/src/epggrab/opentv.h new file mode 100644 index 00000000..e46525d9 --- /dev/null +++ b/src/epggrab/opentv.h @@ -0,0 +1,29 @@ +/* + * Electronic Program Guide - opentv + * Copyright (C) 2012 Adam Sutton + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TVH_EPGGRAB_OPENTV_H__ +#define __TVH_EPGGRAB_OPENTV_H__ + +#include "dvb/dvb.h" +#include "epggrab.h" + +void opentv_tune ( th_dvb_mux_instance_t *tdmi ); +void opentv_init ( epggrab_module_list_t *list ); +void opentv_load ( void ); + +#endif /* __TVH_EPGGRAB_OPENTV_H__ */ diff --git a/src/epggrab/pyepg.c b/src/epggrab/pyepg.c index 827ac798..72760838 100644 --- a/src/epggrab/pyepg.c +++ b/src/epggrab/pyepg.c @@ -427,7 +427,7 @@ void pyepg_init ( epggrab_module_list_t *list ) mod->ch_add = epggrab_module_channel_add; mod->ch_rem = epggrab_module_channel_rem; mod->ch_mod = epggrab_module_channel_mod; - *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_SIMPLE; + *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_INTERNAL; LIST_INSERT_HEAD(list, mod, link); _pyepg_module = mod; diff --git a/src/epggrab/xmltv.c b/src/epggrab/xmltv.c index 0b959298..3cabdc9e 100644 --- a/src/epggrab/xmltv.c +++ b/src/epggrab/xmltv.c @@ -389,7 +389,7 @@ static void _xmltv_load_grabbers ( epggrab_module_list_t *list ) mod->name = malloc(200); mod->channels = &_xmltv_channels; sprintf((char*)mod->name, "XMLTV: %s", &outbuf[n]); - *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_SIMPLE; + *((uint8_t*)&mod->flags) = EPGGRAB_MODULE_INTERNAL; mod->grab = epggrab_module_grab; mod->trans = epggrab_module_trans_xml; mod->parse = _xmltv_parse; diff --git a/src/htsmsg_json.c b/src/htsmsg_json.c index a302247d..89f78f6c 100644 --- a/src/htsmsg_json.c +++ b/src/htsmsg_json.c @@ -164,7 +164,7 @@ htsmsg_json_parse_string(const char *s, const char **endp) if(*s == '\\') { esc = 1; - } else if(*s == '"' && s[-1] != '\\') { + } else if(*s == '"' && (s[-1] != '\\' || s[-2] == '\\')) { *endp = s + 1; @@ -184,6 +184,8 @@ htsmsg_json_parse_string(const char *s, const char **endp) a++; if(*a == 'b') *b++ = '\b'; + else if (*a == '\\') + *b++ = '\\'; else if(*a == 'f') *b++ = '\f'; else if(*a == 'n') @@ -193,9 +195,12 @@ htsmsg_json_parse_string(const char *s, const char **endp) else if(*a == 't') *b++ = '\t'; else if(*a == 'u') { +#if TODO /* 4 hexdigits: Not supported */ free(r); return NULL; +#endif + a += 4; } else { *b++ = *a; } diff --git a/src/huffman.c b/src/huffman.c new file mode 100644 index 00000000..1a72c8e9 --- /dev/null +++ b/src/huffman.c @@ -0,0 +1,112 @@ +/* + * TV headend - Huffman decoder + * Copyb1 (C) 2012 Adam Sutton + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include "huffman.h" +#include "htsmsg.h" +#include "settings.h" + +void huffman_tree_destroy ( huffman_node_t *n ) +{ + if (!n) return; + huffman_tree_destroy(n->b0); + huffman_tree_destroy(n->b1); + if (n->data) free(n->data); + free(n); +} + +huffman_node_t *huffman_tree_load ( const char *path ) +{ + htsmsg_t *m; + huffman_node_t *ret; + if (!(m = hts_settings_load(path))) return NULL; + ret = huffman_tree_build(m); + htsmsg_destroy(m); + return ret; +} + +huffman_node_t *huffman_tree_build ( htsmsg_t *m ) +{ + const char *code, *data, *c; + htsmsg_t *e; + htsmsg_field_t *f; + huffman_node_t *root = calloc(1, sizeof(huffman_node_t)); + HTSMSG_FOREACH(f, m) { + e = htsmsg_get_map_by_field(f); + c = code = htsmsg_get_str(e, "code"); + data = htsmsg_get_str(e, "data"); + if (code && data) { + huffman_node_t *node = root; + while (*c) { + if ( *c == '0' ) { + if (!node->b0) node->b0 = calloc(1, sizeof(huffman_node_t)); + node = node->b0; + } else if ( *c == '1' ) { + if (!node->b1) node->b1 = calloc(1, sizeof(huffman_node_t)); + node = node->b1; + } else { + htsmsg_destroy(m); + huffman_tree_destroy(root); + return NULL; + } + c++; + } + node->data = strdup(data); + } + } + return root; +} + +char *huffman_decode + ( huffman_node_t *tree, const uint8_t *data, size_t len, uint8_t mask, + char *outb, int outl ) +{ + char *ret = outb; + huffman_node_t *node = tree; + if (!len) return NULL; + + outl--; // leave space for NULL + while (len) { + len--; + while (mask) { + if (*data & mask) { + node = node->b1; + } else { + node = node->b0; + } + mask >>= 1; + if (!node) goto end; + if (node->data) { + char *t = node->data; + while (*t && outl) { + *outb = *t; + outb++; t++; outl--; + } + if (!outl) goto end; + node = tree; + } + } + mask = 0x80; + data++; + } +end: + *outb = '\0'; + return ret; +} diff --git a/src/huffman.h b/src/huffman.h new file mode 100644 index 00000000..184a5a8f --- /dev/null +++ b/src/huffman.h @@ -0,0 +1,39 @@ +/* + * TV headend - Huffman decoder + * Copyright (C) 2012 Adam Sutton + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __TVH_HUFFMAN_H__ +#define __TVH_HUFFMAN_H__ + +#include +#include "htsmsg.h" + +typedef struct huffman_node +{ + struct huffman_node *b0; + struct huffman_node *b1; + char *data; +} huffman_node_t; + +void huffman_tree_destroy ( huffman_node_t *tree ); +huffman_node_t *huffman_tree_load ( const char *path ); +huffman_node_t *huffman_tree_build ( htsmsg_t *codes ); +char *huffman_decode + ( huffman_node_t *tree, const uint8_t *data, size_t len, uint8_t mask, + char *outb, int outl ); + +#endif diff --git a/src/webui/extjs.c b/src/webui/extjs.c index ee86d89c..d0dcdfbe 100644 --- a/src/webui/extjs.c +++ b/src/webui/extjs.c @@ -486,7 +486,6 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque) pthread_mutex_lock(&epggrab_mutex); r = htsmsg_create_map(); - htsmsg_add_u32(r, "eitenabled", epggrab_eitenabled); if (epggrab_module) htsmsg_add_str(r, "module", epggrab_module->id); htsmsg_add_u32(r, "interval", epggrab_interval); @@ -514,8 +513,6 @@ extjs_epggrab(http_connection_t *hc, const char *remain, void *opaque) } else if (!strcmp(op, "saveSettings") ) { int save = 0; pthread_mutex_lock(&epggrab_mutex); - u32 = http_arg_get(&hc->hc_req_args, "eitenabled") ? 1 : 0; - save |= epggrab_set_eitenabled(u32); if ( (str = http_arg_get(&hc->hc_req_args, "interval")) ) save |= epggrab_set_interval(atoi(str)); if ( (str = http_arg_get(&hc->hc_req_args, "module")) ) diff --git a/src/webui/static/app/epggrab.js b/src/webui/static/app/epggrab.js index f889b476..30c30b71 100644 --- a/src/webui/static/app/epggrab.js +++ b/src/webui/static/app/epggrab.js @@ -7,8 +7,9 @@ tvheadend.epggrab = function() { /* * Module lists (I'm sure there is a better way!) */ - var EPGGRAB_MODULE_SIMPLE = 0x01; + var EPGGRAB_MODULE_INTERNAL = 0x01; var EPGGRAB_MODULE_EXTERNAL = 0x02; + var EPGGRAB_MODULE_OTA = 0x04; var moduleStore = new Ext.data.JsonStore({ root : 'entries', @@ -17,20 +18,23 @@ tvheadend.epggrab = function() { autoLoad : true, fields : [ 'id', 'name', 'path', 'flags', 'enabled' ] }); - var simpleModuleStore = new Ext.data.Store({ + var internalModuleStore = new Ext.data.Store({ recordType: moduleStore.recordType }); var externalModuleStore = new Ext.data.Store({ recordType: moduleStore.recordType }); + var otaModuleStore = new Ext.data.Store({ + recordType: moduleStore.recordType + }); moduleStore.on('load', function() { moduleStore.filterBy(function(r) { - return r.get('flags') & EPGGRAB_MODULE_SIMPLE; + return r.get('flags') & EPGGRAB_MODULE_INTERNAL; }); - r = new simpleModuleStore.recordType({ id: '', name : 'Disabled'}); - simpleModuleStore.add(r); + r = new internalModuleStore.recordType({ id: '', name : 'Disabled'}); + internalModuleStore.add(r); moduleStore.each(function(r) { - simpleModuleStore.add(r.copy()); + internalModuleStore.add(r.copy()); }); moduleStore.filterBy(function(r) { return r.get('flags') & EPGGRAB_MODULE_EXTERNAL; @@ -38,6 +42,12 @@ tvheadend.epggrab = function() { moduleStore.each(function(r) { externalModuleStore.add(r.copy()); }); + moduleStore.filterBy(function(r) { + return r.get('flags') & EPGGRAB_MODULE_OTA; + }); + moduleStore.each(function(r) { + otaModuleStore.add(r.copy()); + }); }); /* @@ -46,7 +56,7 @@ tvheadend.epggrab = function() { var confreader = new Ext.data.JsonReader( { root: 'epggrabSettings' }, - [ 'module', 'eitenabled', 'advanced', 'interval' ] + [ 'module', 'interval' ] ); /* **************************************************************** @@ -56,7 +66,7 @@ tvheadend.epggrab = function() { /* * Module selector */ - var simpleModule = new Ext.form.ComboBox({ + var internalModule = new Ext.form.ComboBox({ fieldLabel : 'Module', hiddenName : 'module', width : 300, @@ -66,7 +76,7 @@ tvheadend.epggrab = function() { editable : false, mode : 'local', triggerAction : 'all', - store : simpleModuleStore + store : internalModuleStore }); /* @@ -182,9 +192,38 @@ tvheadend.epggrab = function() { }, iconCls : 'icon-grid', }); - var advancedPanel = externalGrid; + + /* + * OTA modules + */ + + var otaSelectionModel = new Ext.grid.CheckboxSelectionModel({ + singleSelect : false, + listeners : { + 'rowselect' : function (s, ri, r) { + r.set('enabled', 1); + }, + 'rowdeselect' : function (s, ri, r) { + r.set('enabled', 0); + } + } + }); + + var otaGrid = new Ext.grid.EditorGridPanel({ + store : otaModuleStore, + cm : externalColumnModel, + sm : otaSelectionModel, + width : 600, + height : 150, + frame : false, + viewConfig : { + forceFit : true, + }, + iconCls : 'icon-grid', + }); + + /* HACK: get display working */ externalGrid.on('render', function(){ - // TODO: bit of hack to get selection working delay = new Ext.util.DelayedTask(function(){ rows = []; externalModuleStore.each(function(r){ @@ -194,16 +233,21 @@ tvheadend.epggrab = function() { }); delay.delay(100); }); + otaGrid.on('render', function(){ + delay = new Ext.util.DelayedTask(function(){ + rows = []; + otaModuleStore.each(function(r){ + if (r.get('enabled')) rows.push(r); + }); + otaSelectionModel.selectRecords(rows); + }); + delay.delay(100); + }); /* **************************************************************** * Form * ***************************************************************/ - var eitCheck = new Ext.form.Checkbox({ - fieldLabel : 'EIT Enabled', - name : 'eitenabled' - }); - var saveButton = new Ext.Button({ text : "Save configuration", tooltip : 'Save changes made to configuration below', @@ -231,12 +275,13 @@ tvheadend.epggrab = function() { defaultType : 'textfield', items : [ interval, - eitCheck, - simpleModule, + internalModule, intervalValue, intervalUnit, new Ext.form.Label({text: 'External Interfaces'}), - advancedPanel + externalGrid, + new Ext.form.Label({text: 'OTA Modules'}), + otaGrid, ], tbar: [ saveButton, @@ -264,6 +309,9 @@ tvheadend.epggrab = function() { externalModuleStore.each(function(r) { mods.push({id: r.get('id'), enabled: r.get('enabled') ? 1 : 0}); }); + otaModuleStore.each(function(r) { + mods.push({id: r.get('id'), enabled: r.get('enabled') ? 1 : 0}); + }); mods = Ext.util.JSON.encode(mods); confpanel.getForm().submit({ url : 'epggrab', @@ -280,4 +328,3 @@ tvheadend.epggrab = function() { return confpanel; } -