Index: action.py ================================================================== --- action.py +++ action.py @@ -1,12 +1,12 @@ # encoding: UTF-8 # api: streamtuner2 # type: functions -# cagtegory: io +# category: io # title: play/record actions # description: Starts audio applications, guesses MIME types for URLs -# version: 0.9 +# version: 1.0 # priority: core # # Multimedia interface for starting audio players, recording app, # or web browser (listed as "url/http" association in players). # It maps audio MIME types, and extracts/converts playlist types @@ -176,13 +176,14 @@ # Convert MIME type into list of ["audio/xyz", "audio/*", "*/*"] # for comparison against configured record/play association. def mime_app(fmt, cmd_list): major = fmt[:fmt.find("/")] - for match in [ fmt, major + "/*", "*/*" ]: + for match in [ fmt, major + "/*", "*/*", "video/*", "audio/*" ]: if cmd_list.get(match): return cmd_list[match] + log.ERR("No audio player for stream type found") # Replaces instances of %m3u, %pls, %srv in a command string. # · Also understands short aliases %l, %f, %d. @@ -189,30 +190,29 @@ # · And can embed %title or %genre placeholders. # · Replace .pls URL with local .m3u file depending on map. # def interpol(cmd, url, source="pls", row={}): - # inject other meta fields + # Inject other meta fields (%title, %genre, %playing, %format, etc.) row = copy.copy(row) - if row: - for field in row: - cmd = cmd.replace("%"+field, "%r" % row.get(field)) + for field in set(re.findall("%(\w+)", cmd)).intersection(row.keys()): + cmd = cmd.replace("%"+field, "%r" % row.get(field)) - # add default if cmd has no %url placeholder + # Add default %pls if cmd has no %url placeholder if cmd.find("%") < 0: cmd = cmd + " %pls" # "pls" as default requires no conversion for most channels, and seems broadly supported by players - # standard placeholders + # Playlist type placeholders (%pls, %m3u, %xspf, etc.) for dest, rx in placeholder_map.items(): if re.search(rx, cmd, re.X): # from .pls to .m3u fn_or_urls = convert_playlist(url, listfmt(source), listfmt(dest), local_file=True, row=row) # insert quoted URL/filepath return re.sub(rx, quote(fn_or_urls), cmd, 2, re.X) - return "false" + return "/bin/false" # Substitute .pls URL with local .m3u, or direct srv addresses, or leaves URL asis. # · Takes a single input `url` (and original row{} as template). # · But returns a list of [urls] after playlist extraction. @@ -349,11 +349,11 @@ if not fmt: fmt = self.probe_fmt() log.DATA("input extractor/regex:", fmt, len(self.src)) # specific extractor implementations - if fmt in dir(self): + if hasattr(self, fmt): try: return getattr(self, fmt)() except Exception as e: log.WARN("Native {} parser failed on input (improper encoding, etc)".format(fmt), e) @@ -454,12 +454,15 @@ url = r" (?x) \"audio\" \s*:\s* \"(\w+:\\?/\\?/[^\"\s]+)\" ", title = r" (?x) \"name\" \s*:\s* \"([^\"]+)\" ", unesc = "json", ), "json": dict( - url = r" (?x) \"url\" \s*:\s* \"(\w+:\\?/\\?/[^\"\s]+)\" ", - title = r" (?x) \"title\" \s*:\s* \"([^\"]+)\" ", + url = r" (?x) \"(?:url|audio|stream)\" \s*:\s* \"(\w+:\\?/\\?/[^\"\s]+)\" ", + title = r" (?x) \"(?:title|name|station)\" \s*:\s* \"([^\"]+)\" ", + playing = r" (?x) \"(?:playing|current|description)\" \s*:\s* \"([^\"]+)\" ", + homepage= r" (?x) \"(?:homepage|website|info)\" \s*:\s* \"([^\"]+)\" ", + genre = r" (?x) \"(?:genre|keywords|category)\" \s*:\s* \"([^\"]+)\" ", unesc = "json", ), "asf": dict( url = r" (?m) ^ \s*Ref\d+ = (\w+://[^\s]+) ", unesc = "xml", @@ -487,11 +490,11 @@ rows = {} for field,num,value in re.findall("^\s* ([a-z_-]+) (\d+) \s*=\s* (.*) $", self.src, re.M|re.I|re.X): if not num in rows: rows[num] = {} field = fieldmap.get(field.lower()) - if field: + if field and len(value): rows[num][field] = value.strip() return [rows[str(i)] for i in sorted(map(int, rows.keys()))] # Add placeholder fields to extracted row