You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
6.5 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. #!/usr/bin/env python2
  2. ''' Python module to download videos using youtube-dl & subprocess. '''
  3. import os
  4. import sys
  5. import locale
  6. import subprocess
  7. class YoutubeDLDownloader(object):
  8. '''
  9. OUT_OF_DATE
  10. Download videos using youtube-dl & subprocess.
  11. Params
  12. youtubedl_path: Absolute path of youtube-dl.
  13. data_hook: Can be any function with one parameter, the data.
  14. log_manager: Can be any log_manager which implements log().
  15. Accessible Methods
  16. download()
  17. Params: URL to download
  18. Options list e.g. ['--help']
  19. Return: DownlaodObject.OK
  20. YoutubeDLDownloader.ERROR
  21. YoutubeDLDownloader.STOPPED
  22. YoutubeDLDownloader.ALREADY
  23. stop()
  24. Params: None
  25. Return: None
  26. Data_hook Keys
  27. 'playlist_index',
  28. 'playlist_size',
  29. 'filesize',
  30. 'filename',
  31. 'percent',
  32. 'status',
  33. 'speed',
  34. 'eta'
  35. '''
  36. # download() return codes
  37. OK = 0
  38. ERROR = 1
  39. STOPPED = 2
  40. ALREADY = 3
  41. def __init__(self, youtubedl_path, data_hook=None, log_manager=None):
  42. self.youtubedl_path = youtubedl_path
  43. self.log_manager = log_manager
  44. self.data_hook = data_hook
  45. self._return_code = 0
  46. self._proc = None
  47. self._data = {
  48. 'playlist_index': None,
  49. 'playlist_size': None,
  50. 'filesize': None,
  51. 'filename': None,
  52. 'percent': None,
  53. 'status': None,
  54. 'speed': None,
  55. 'eta': None
  56. }
  57. def download(self, url, options):
  58. ''' Download given url using youtube-dl &
  59. return self._return_code.
  60. '''
  61. self._return_code = self.OK
  62. cmd = self._get_cmd(url, options)
  63. self._create_process(cmd)
  64. while self._proc_is_alive():
  65. stdout, stderr = self._read()
  66. data = extract_data(stdout)
  67. if self._update_data(data):
  68. self._hook_data()
  69. if stderr:
  70. self._return_code = self.ERROR
  71. self._log(stderr)
  72. return self._return_code
  73. def stop(self):
  74. ''' Stop downloading. '''
  75. if self._proc_is_alive():
  76. self._proc.kill()
  77. self._return_code = self.STOPPED
  78. def _update_data(self, data):
  79. ''' Update self._data from data.
  80. Return True if updated else return False.
  81. '''
  82. updated = False
  83. for key in data:
  84. if key == 'filename':
  85. # Keep only the filename on data['filename']
  86. data['filename'] = os.path.basename(data['filename'])
  87. if key == 'status' and data['status'] == 'Already Downloaded':
  88. # Set self._return_code to already downloaded
  89. # and trash that key
  90. self._return_code = self.ALREADY
  91. data['status'] = None
  92. self._data[key] = data[key]
  93. if not updated:
  94. updated = True
  95. return updated
  96. def _log(self, data):
  97. ''' Log data using self.log_manager. '''
  98. if self.log_manager is not None:
  99. self.log_manager.log(data)
  100. def _hook_data(self):
  101. ''' Pass self._data back to data_hook. '''
  102. if self.data_hook is not None:
  103. self.data_hook(self._data)
  104. def _proc_is_alive(self):
  105. ''' Return True if self._proc is alive. '''
  106. if self._proc is None:
  107. return False
  108. return self._proc.poll() is None
  109. def _read(self):
  110. ''' Read subprocess stdout, stderr. '''
  111. stdout = stderr = ''
  112. stdout = self._read_stream(self._proc.stdout)
  113. if not stdout:
  114. stderr = self._read_stream(self._proc.stderr)
  115. return stdout, stderr
  116. def _read_stream(self, stream):
  117. ''' Read subprocess stream. '''
  118. if self._proc is None:
  119. return ''
  120. return stream.readline().rstrip()
  121. def _get_cmd(self, url, options):
  122. ''' Return command for subprocess. '''
  123. if os.name == 'nt':
  124. cmd = [self.youtubedl_path] + options + [url]
  125. else:
  126. cmd = ['python', self.youtubedl_path] + options + [url]
  127. return cmd
  128. def _create_process(self, cmd):
  129. ''' Create new subprocess. '''
  130. encoding = info = None
  131. # Hide subprocess window on Windows
  132. if os.name == 'nt':
  133. info = subprocess.STARTUPINFO()
  134. info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  135. # Encode command for subprocess
  136. # Refer to http://stackoverflow.com/a/9951851/35070
  137. if sys.version_info < (3, 0) and sys.platform == 'win32':
  138. try:
  139. encoding = locale.getpreferredencoding()
  140. u'TEST'.encode(encoding)
  141. except:
  142. encoding = 'UTF-8'
  143. if encoding is not None:
  144. cmd = [item.encode(encoding, 'ignore') for item in cmd]
  145. self._proc = subprocess.Popen(cmd,
  146. stdout=subprocess.PIPE,
  147. stderr=subprocess.PIPE,
  148. startupinfo=info)
  149. def extract_data(stdout):
  150. ''' Extract data from youtube-dl stdout. '''
  151. data_dictionary = dict()
  152. stdout = [string for string in stdout.split(' ') if string != '']
  153. if len(stdout) == 0:
  154. return data_dictionary
  155. header = stdout.pop(0)
  156. if header == '[download]':
  157. data_dictionary['status'] = 'Downloading'
  158. # Get filename
  159. if stdout[0] == 'Destination:':
  160. data_dictionary['filename'] = ' '.join(stdout[1:])
  161. # Get progress info
  162. if '%' in stdout[0]:
  163. if stdout[0] == '100%':
  164. data_dictionary['speed'] = ''
  165. data_dictionary['eta'] = ''
  166. else:
  167. data_dictionary['percent'] = stdout[0]
  168. data_dictionary['filesize'] = stdout[2]
  169. data_dictionary['speed'] = stdout[4]
  170. data_dictionary['eta'] = stdout[6]
  171. # Get playlist info
  172. if stdout[0] == 'Downloading' and stdout[1] == 'video':
  173. data_dictionary['playlist_index'] = stdout[2]
  174. data_dictionary['playlist_size'] = stdout[4]
  175. # Get file already downloaded status
  176. if stdout[-1] == 'downloaded':
  177. data_dictionary['status'] = 'Already Downloaded'
  178. elif header == '[ffmpeg]':
  179. data_dictionary['status'] = 'Post Processing'
  180. else:
  181. data_dictionary['status'] = 'Pre Processing'
  182. return data_dictionary