1# Copyright (c) 2014-2021 Cedric Bellegarde <cedric.bellegarde@adishatz.org> 2# This program is free software: you can redistribute it and/or modify 3# it under the terms of the GNU General Public License as published by 4# the Free Software Foundation, either version 3 of the License, or 5# (at your option) any later version. 6# This program is distributed in the hope that it will be useful, 7# but WITHOUT ANY WARRANTY; without even the implied warranty of 8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9# GNU General Public License for more details. 10# You should have received a copy of the GNU General Public License 11# along with this program. If not, see <http://www.gnu.org/licenses/>. 12 13from gi.repository import Gio, GLib 14from gi.repository.Gio import FILE_ATTRIBUTE_TIME_ACCESS 15 16from time import time 17 18from lollypop.logger import Logger 19from lollypop.define import App, FileType 20 21 22def get_file_type(uri): 23 """ 24 Get file type from file extension 25 @param uri as str 26 """ 27 audio = ["3gp", "aa", "aac", "aax", "act", "aiff", "alac", "amr", "ape", 28 "au", "awb", "dct", "dss", "dvf", "flac", "gsm", "iklax", "ivs", 29 "m4a", "m4b", "m4p", "mmf", "mp3", "mpc", "msv", "nmf", "nsf", 30 "ogg", "opus", "ra", "raw", "rf64", "sln", "tta", "voc", "vox", 31 "wav", "wma", "wv", "webm", "8svx", "cda"] 32 other = ["7z", "arj", "deb", "pkg", "rar", "rpm", "tar.gz", "z", "zip", 33 "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "db", "txt", 34 "mov", "avi", "html", "ini", "cue", "nfo", "lrc"] 35 image = ["ai", "bmp", "gif", "ico", "jpeg", "jpg", 36 "png", "ps", "psd", "svg", "tif"] 37 pls = ["pls", "m3u"] 38 split = uri.lower().split(".") 39 if len(split) < 2: 40 return FileType.UNKNOWN 41 elif split[-1] in audio: 42 return FileType.AUDIO 43 elif split[-1] in pls: 44 return FileType.PLS 45 elif split[-1] in image + other: 46 return FileType.OTHER 47 else: 48 return FileType.UNKNOWN 49 50 51def is_audio(info): 52 """ 53 Return True if files is audio 54 @param info as Gio.FileInfo 55 """ 56 audio = ["application/ogg", "application/x-ogg", "application/x-ogm-audio", 57 "audio/aac", "audio/mp4", "audio/mpeg", "audio/mpegurl", 58 "audio/ogg", "audio/vnd.rn-realaudio", "audio/vorbis", 59 "audio/x-flac", "audio/x-mp3", "audio/x-mpeg", "audio/x-mpegurl", 60 "audio/x-ms-wma", "audio/x-musepack", "audio/x-oggflac", 61 "audio/x-pn-realaudio", "application/x-flac", "audio/x-speex", 62 "audio/x-vorbis", "audio/x-vorbis+ogg", "audio/x-wav", 63 "x-content/audio-player", "audio/x-aac", "audio/m4a", 64 "audio/x-m4a", "audio/mp3", "audio/ac3", "audio/flac", 65 "audio/x-opus+ogg", "application/x-extension-mp4", "audio/x-ape", 66 "audio/x-pn-aiff", "audio/x-pn-au", "audio/x-pn-wav", 67 "audio/x-pn-windows-acm", "application/x-matroska", 68 "audio/x-matroska", "audio/x-wavpack", "video/mp4", 69 "audio/x-mod", "audio/x-mo3", "audio/x-xm", "audio/x-s3m", 70 "audio/x-it", "audio/aiff", "audio/x-aiff"] 71 if info is not None: 72 if info.get_content_type() in audio: 73 return True 74 return False 75 76 77def is_pls(info): 78 """ 79 Return True if files is a playlist 80 @param info as Gio.FileInfo 81 """ 82 if info is not None: 83 if info.get_content_type() in ["audio/x-mpegurl", 84 "application/xspf+xml", 85 "application/vnd.apple.mpegurl"]: 86 return True 87 return False 88 89 90def get_mtime(info): 91 """ 92 Return Last modified time of a given file 93 @param info as Gio.FileInfo 94 """ 95 mtime = info.get_attribute_as_string("time::modified") 96 if mtime is None: 97 return 0 98 else: 99 return int(mtime) 100 101 102def remove_oldest(path, timestamp): 103 """ 104 Remove oldest files at path 105 @param path as str 106 @param timestamp as int 107 """ 108 SCAN_QUERY_INFO = "%s" % FILE_ATTRIBUTE_TIME_ACCESS 109 try: 110 d = Gio.File.new_for_path(path) 111 infos = d.enumerate_children(SCAN_QUERY_INFO, 112 Gio.FileQueryInfoFlags.NONE, 113 None) 114 for info in infos: 115 f = infos.get_child(info) 116 if info.get_file_type() == Gio.FileType.REGULAR: 117 atime = int(info.get_attribute_as_string( 118 FILE_ATTRIBUTE_TIME_ACCESS)) 119 # File not used since one year 120 if time() - atime > timestamp: 121 f.delete(None) 122 except Exception as e: 123 Logger.error("remove_oldest(): %s", e) 124 125 126def is_readonly(uri): 127 """ 128 Check if uri is readonly 129 """ 130 try: 131 if not uri: 132 return True 133 f = Gio.File.new_for_uri(uri) 134 info = f.query_info("access::can-write", 135 Gio.FileQueryInfoFlags.NONE, 136 None) 137 return not info.get_attribute_boolean("access::can-write") 138 except: 139 return True 140 141 142def create_dir(path): 143 """ 144 Create dir 145 @param path as str 146 """ 147 d = Gio.File.new_for_path(path) 148 if not d.query_exists(): 149 try: 150 d.make_directory_with_parents() 151 except: 152 Logger.info("Can't create %s" % path) 153 154 155def install_youtube_dl(): 156 try: 157 path = GLib.get_user_data_dir() + "/lollypop/python" 158 argv = ["pip3", "install", "-t", path, "-U", "youtube-dl"] 159 GLib.spawn_sync(None, argv, [], GLib.SpawnFlags.SEARCH_PATH, None) 160 except Exception as e: 161 Logger.error("install_youtube_dl: %s" % e) 162 163 164def get_youtube_dl(): 165 """ 166 Get youtube-dl path and env 167 @return (str, []) 168 """ 169 if App().settings.get_value("recent-youtube-dl"): 170 python_path = GLib.get_user_data_dir() + "/lollypop/python" 171 path = "%s/bin/youtube-dl" % python_path 172 env = ["PYTHONPATH=%s" % python_path] 173 f = Gio.File.new_for_path(path) 174 if f.query_exists(): 175 return (path, env) 176 if GLib.find_program_in_path("youtube-dl"): 177 return ("youtube-dl", []) 178 else: 179 return (None, []) 180 181 182# From eyeD3 start 183# eyeD3 is written and maintained by: 184# Travis Shirk <travis@pobox.com> 185def id3EncodingToString(encoding): 186 from lollypop.define import LATIN1_ENCODING, UTF_8_ENCODING 187 from lollypop.define import UTF_16_ENCODING, UTF_16BE_ENCODING 188 if encoding == LATIN1_ENCODING: 189 return "latin_1" 190 elif encoding == UTF_8_ENCODING: 191 return "utf_8" 192 elif encoding == UTF_16_ENCODING: 193 return "utf_16" 194 elif encoding == UTF_16BE_ENCODING: 195 return "utf_16_be" 196 else: 197 raise ValueError("Encoding unknown: %s" % encoding) 198 199 200# From eyeD3 start 201def decodeUnicode(bites, encoding): 202 codec = id3EncodingToString(encoding) 203 return bites.decode(codec) 204 205 206def splitUnicode(data, encoding): 207 from lollypop.define import UTF_16_ENCODING, UTF_16BE_ENCODING 208 try: 209 (d, t) = data.split(encoding, 1) 210 # Try to fix invalid UTF16 211 if encoding == UTF_16_ENCODING or encoding == UTF_16BE_ENCODING: 212 if t[1] == 0: 213 t = b''.join(t.split(b'\x00')) + b'\x00' 214 except ValueError as e: 215 Logger.warning("splitUnicode(): %s", e) 216 (d, t) = data, b"" 217 return (d, t) 218# From eyeD3 end 219