├── Assets ├── background.png ├── background2.png ├── cancel.png ├── down.png ├── end.png ├── forward.png ├── library.png ├── minus.png ├── ok.png ├── pause_off.png ├── pause_on.png ├── play_off.png ├── play_on.png ├── player.ico ├── playlist.png ├── plus.png ├── rewind.png ├── sound_off.png ├── sound_on.png ├── start.png ├── stop.png └── up.png ├── Examples ├── Mini │ ├── playing.png │ ├── playing_rpi.png │ └── startup.png ├── paused.PNG ├── playing1.png ├── playing2.PNG ├── playlist.png └── startup.PNG ├── Python-VLC-Quick-Start.ipynb ├── README.md ├── Tutorial-Assets ├── vlc1.png ├── vlc2.png ├── vlc3.png └── vlc4.png ├── playlist.txt └── vlc_media_player.py /Assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/background.png -------------------------------------------------------------------------------- /Assets/background2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/background2.png -------------------------------------------------------------------------------- /Assets/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/cancel.png -------------------------------------------------------------------------------- /Assets/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/down.png -------------------------------------------------------------------------------- /Assets/end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/end.png -------------------------------------------------------------------------------- /Assets/forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/forward.png -------------------------------------------------------------------------------- /Assets/library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/library.png -------------------------------------------------------------------------------- /Assets/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/minus.png -------------------------------------------------------------------------------- /Assets/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/ok.png -------------------------------------------------------------------------------- /Assets/pause_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/pause_off.png -------------------------------------------------------------------------------- /Assets/pause_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/pause_on.png -------------------------------------------------------------------------------- /Assets/play_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/play_off.png -------------------------------------------------------------------------------- /Assets/play_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/play_on.png -------------------------------------------------------------------------------- /Assets/player.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/player.ico -------------------------------------------------------------------------------- /Assets/playlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/playlist.png -------------------------------------------------------------------------------- /Assets/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/plus.png -------------------------------------------------------------------------------- /Assets/rewind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/rewind.png -------------------------------------------------------------------------------- /Assets/sound_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/sound_off.png -------------------------------------------------------------------------------- /Assets/sound_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/sound_on.png -------------------------------------------------------------------------------- /Assets/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/start.png -------------------------------------------------------------------------------- /Assets/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/stop.png -------------------------------------------------------------------------------- /Assets/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Assets/up.png -------------------------------------------------------------------------------- /Examples/Mini/playing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Examples/Mini/playing.png -------------------------------------------------------------------------------- /Examples/Mini/playing_rpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Examples/Mini/playing_rpi.png -------------------------------------------------------------------------------- /Examples/Mini/startup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Examples/Mini/startup.png -------------------------------------------------------------------------------- /Examples/paused.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Examples/paused.PNG -------------------------------------------------------------------------------- /Examples/playing1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Examples/playing1.png -------------------------------------------------------------------------------- /Examples/playing2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Examples/playing2.PNG -------------------------------------------------------------------------------- /Examples/playlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Examples/playlist.png -------------------------------------------------------------------------------- /Examples/startup.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Examples/startup.PNG -------------------------------------------------------------------------------- /Python-VLC-Quick-Start.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Python-VLC Quick-Start Tutorial\n", 8 | "This notebook presents a very high-level tutorial for using several Python-VLC classes and methods" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": 1, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "import vlc\n", 18 | "import pafy" 19 | ] 20 | }, 21 | { 22 | "cell_type": "markdown", 23 | "metadata": {}, 24 | "source": [ 25 | "## Instance\n", 26 | "First, create a vlc instance that will be used to create all other media player objects" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 2, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "instance = vlc.Instance()" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "Next, initialize a new player. In this case we'll start it up already loaded with a video." 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 3, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "player = instance.media_player_new('./Tutorial-Assets/notepad.mp4')" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "Use the `play` method to start playing the video" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 4, 64 | "metadata": {}, 65 | "outputs": [ 66 | { 67 | "data": { 68 | "text/plain": [ 69 | "0" 70 | ] 71 | }, 72 | "execution_count": 4, 73 | "metadata": {}, 74 | "output_type": "execute_result" 75 | } 76 | ], 77 | "source": [ 78 | "player.play()" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "![](./Tutorial-Assets/vlc1.png)" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": {}, 91 | "source": [ 92 | "Next, you can stop the video with the `stop` method" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 5, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "player.stop()" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "## MediaPlayer methods\n", 109 | "```python\n", 110 | "player.pause()\n", 111 | "player.play()\n", 112 | "\n", 113 | "player.get_length() # the duration of the video (in ms)\n", 114 | "player.get_time() # the current time of the movie (in ms)\n", 115 | "player.get_position() # get movie position as percentage between 0.0 and 1.0\n", 116 | "player.set_position() # set movie position as percentage between 0.0 and 1.0\n", 117 | "\n", 118 | "player.audio_get_volume() # get volume in percents (0=mute, 100=nominal)\n", 119 | "player.audio_set_volume() # set volume in percents (0=mute, 100=nominal)\n", 120 | "player.audio_toggle_mute() # toggle mute status\n", 121 | "\n", 122 | "player.is_playing() # 1 if playing 0 otherwise\n", 123 | "player.get_state() # get current state of player (playing, paused, etc...)\n", 124 | "```" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "## Media\n", 132 | "You can deal with media objects directly. This is very helpful when you want to set the media on an existing player." 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 6, 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [ 141 | "pysimplegui_video = instance.media_new('./videos/file-search-engine.mp4')" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 7, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "player.set_media(pysimplegui_video)" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 8, 156 | "metadata": {}, 157 | "outputs": [ 158 | { 159 | "data": { 160 | "text/plain": [ 161 | "0" 162 | ] 163 | }, 164 | "execution_count": 8, 165 | "metadata": {}, 166 | "output_type": "execute_result" 167 | } 168 | ], 169 | "source": [ 170 | "player.play()" 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "metadata": {}, 176 | "source": [ 177 | "![](./Tutorial-Assets/vlc2.png)" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": 9, 183 | "metadata": {}, 184 | "outputs": [], 185 | "source": [ 186 | "player.stop()" 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "metadata": {}, 192 | "source": [ 193 | "## List Player\n", 194 | "As the name suggests, this is player that handles a list of media objects. You will also need create a media list and assign the media list to the player." 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 10, 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [ 203 | "# Create the list player\n", 204 | "list_player = instance.media_list_player_new()\n", 205 | "\n", 206 | "# Create the media list; this one we will instantiate with an empty list\n", 207 | "media_list = instance.media_list_new([])" 208 | ] 209 | }, 210 | { 211 | "cell_type": "markdown", 212 | "metadata": {}, 213 | "source": [ 214 | "Next, I'm going to add several videos to my `media_list` object. You can also iterate through a directory if you have many files to load." 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": 11, 220 | "metadata": {}, 221 | "outputs": [ 222 | { 223 | "data": { 224 | "text/plain": [ 225 | "0" 226 | ] 227 | }, 228 | "execution_count": 11, 229 | "metadata": {}, 230 | "output_type": "execute_result" 231 | } 232 | ], 233 | "source": [ 234 | "media_list.add_media('./videos/data-logger-csv.mp4')\n", 235 | "media_list.add_media('./videos/file-search-engine')\n", 236 | "media_list.add_media('./videos/notepad.mp4')" 237 | ] 238 | }, 239 | { 240 | "cell_type": "markdown", 241 | "metadata": {}, 242 | "source": [ 243 | "Finally, assign the `media_list` to the `list_player` and use the `play` method to start playing the videos." 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": 12, 249 | "metadata": {}, 250 | "outputs": [], 251 | "source": [ 252 | "# Assign the media list to the list player\n", 253 | "list_player.set_media_list(media_list)\n", 254 | "\n", 255 | "# Begin playback on the media list\n", 256 | "list_player.play()" 257 | ] 258 | }, 259 | { 260 | "cell_type": "markdown", 261 | "metadata": {}, 262 | "source": [ 263 | "![](./Tutorial-Assets/vlc3.png)" 264 | ] 265 | }, 266 | { 267 | "cell_type": "code", 268 | "execution_count": 13, 269 | "metadata": {}, 270 | "outputs": [], 271 | "source": [ 272 | "list_player.stop()" 273 | ] 274 | }, 275 | { 276 | "cell_type": "markdown", 277 | "metadata": {}, 278 | "source": [ 279 | "## ListPlayer methods\n", 280 | "```python\n", 281 | "list_player.play()\n", 282 | "list_player.stop()\n", 283 | "list_player.pause()\n", 284 | "list_player.next() # play the next item in the media list\n", 285 | "list_player.previous() # play the previous item in the media list\n", 286 | "list_player.play_item_at_index(2) # play a specific item by index\n", 287 | "\n", 288 | "list_player.get_state() # state for the media list player (playing, stopped, etc...)\n", 289 | "list_player.is_playing() # 1 if playing 0 otherwise\n", 290 | "```\n", 291 | "\n", 292 | "## MediaList methods\n", 293 | "```python\n", 294 | "media_list.remove_index(0) # remove a specific media item by index\n", 295 | "media_list.count() # a count of items in the media list\n", 296 | "media_list.add_media(media) # used to add additional items to the media list\n", 297 | "```" 298 | ] 299 | }, 300 | { 301 | "cell_type": "markdown", 302 | "metadata": {}, 303 | "source": [ 304 | "## Meta Data\n", 305 | "You can store various kinds of meta data within media objects. For example:" 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": 14, 311 | "metadata": {}, 312 | "outputs": [ 313 | { 314 | "data": { 315 | "text/plain": [ 316 | "{0: 'Title',\n", 317 | " 1: 'Artist',\n", 318 | " 2: 'Genre',\n", 319 | " 3: 'Copyright',\n", 320 | " 4: 'Album',\n", 321 | " 5: 'TrackNumber',\n", 322 | " 6: 'Description',\n", 323 | " 7: 'Rating',\n", 324 | " 8: 'Date',\n", 325 | " 9: 'Setting',\n", 326 | " 10: 'URL',\n", 327 | " 11: 'Language',\n", 328 | " 12: 'NowPlaying',\n", 329 | " 13: 'Publisher',\n", 330 | " 14: 'EncodedBy',\n", 331 | " 15: 'ArtworkURL',\n", 332 | " 16: 'TrackID',\n", 333 | " 17: 'TrackTotal',\n", 334 | " 18: 'Director',\n", 335 | " 19: 'Season',\n", 336 | " 20: 'Episode',\n", 337 | " 21: 'ShowName',\n", 338 | " 22: 'Actors',\n", 339 | " 23: 'AlbumArtist',\n", 340 | " 24: 'DiscNumber',\n", 341 | " 25: 'DiscTotal'}" 342 | ] 343 | }, 344 | "execution_count": 14, 345 | "metadata": {}, 346 | "output_type": "execute_result" 347 | } 348 | ], 349 | "source": [ 350 | "vlc.Meta._enum_names_" 351 | ] 352 | }, 353 | { 354 | "cell_type": "markdown", 355 | "metadata": {}, 356 | "source": [ 357 | "As an example, I will add meta data to the 3 videos that are current in the media_list. I can access each media item by index, and then use the `set_meta` method to store the meta data using the keys in the dictionary printed above." 358 | ] 359 | }, 360 | { 361 | "cell_type": "code", 362 | "execution_count": 15, 363 | "metadata": {}, 364 | "outputs": [], 365 | "source": [ 366 | "# Set the meta data for video #1\n", 367 | "media_list[0].set_meta(0, 'Build a CSV Data Logger for Raspberry Pi') # Title\n", 368 | "media_list[0].set_meta(1, 'Israel Dryer') # Author\n", 369 | "\n", 370 | "# Set the meta data for video #2\n", 371 | "media_list[1].set_meta(0, 'How to build a file search engine with PySimpleGUI') # Title\n", 372 | "media_list[1].set_meta(1, 'Israel Dryer') # Author\n", 373 | "\n", 374 | "# Set the meta data for video #3\n", 375 | "media_list[2].set_meta(0, 'How to build a Notepad with PySimpleGUI') # Title\n", 376 | "media_list[2].set_meta(1, 'Israel Dryer') # Author" 377 | ] 378 | }, 379 | { 380 | "cell_type": "markdown", 381 | "metadata": {}, 382 | "source": [ 383 | "Now that the meta data is saved, I can access it as needed with the `get_meta` method." 384 | ] 385 | }, 386 | { 387 | "cell_type": "code", 388 | "execution_count": 16, 389 | "metadata": {}, 390 | "outputs": [ 391 | { 392 | "name": "stdout", 393 | "output_type": "stream", 394 | "text": [ 395 | "Title: Build a CSV Data Logger for Raspberry Pi\n", 396 | "Author: Israel Dryer\n", 397 | "\n", 398 | "Title: How to build a file search engine with PySimpleGUI\n", 399 | "Author: Israel Dryer\n", 400 | "\n", 401 | "Title: How to build a Notepad with PySimpleGUI\n", 402 | "Author: Israel Dryer\n", 403 | "\n" 404 | ] 405 | } 406 | ], 407 | "source": [ 408 | "for video in media_list:\n", 409 | " print('Title:', video.get_meta(0))\n", 410 | " print('Author:', video.get_meta(1)) \n", 411 | " print()" 412 | ] 413 | }, 414 | { 415 | "cell_type": "markdown", 416 | "metadata": {}, 417 | "source": [ 418 | "## The typical build order\n", 419 | "There are a lot of different ways to create media players, media, instances, etc... However, the recommended order for building these objects is as follows:\n", 420 | "- instance\n", 421 | "- media_list_player\n", 422 | "- media_list\n", 423 | "- media_player\n", 424 | "\n", 425 | "You might be suprised by the last item, but you can actually get a media_player object from the media_list player, as it is what is playing the items in the list_player. The list player gives you a certain amount of control over the media, however, the player gives you a little more power to control things such as volume, position, etc... of the items currently being played.\n", 426 | "\n", 427 | "For example, if you want to get the meta data for the media that is currently playing in the media player you could get it as follows:" 428 | ] 429 | }, 430 | { 431 | "cell_type": "code", 432 | "execution_count": 17, 433 | "metadata": {}, 434 | "outputs": [ 435 | { 436 | "name": "stdout", 437 | "output_type": "stream", 438 | "text": [ 439 | "Title: Build a CSV Data Logger for Raspberry Pi\n", 440 | "Author: Israel Dryer\n" 441 | ] 442 | } 443 | ], 444 | "source": [ 445 | "# Get the media player that is playing the media for the list player\n", 446 | "media_player = list_player.get_media_player()\n", 447 | "\n", 448 | "# Get the current media loaded on the media player\n", 449 | "curr_video = media_player.get_media()\n", 450 | "\n", 451 | "# Retrieve the meta data from the current video\n", 452 | "print('Title:', curr_video.get_meta(0))\n", 453 | "print('Author:', curr_video.get_meta(1))" 454 | ] 455 | }, 456 | { 457 | "cell_type": "markdown", 458 | "metadata": {}, 459 | "source": [ 460 | "## Streaming & Online Media (ie. YouTube)\n", 461 | "In order to play streaming or online media with your VLC Player, you'll need to leverage another library called **Pafy**. The process is really no different except Pafy will give you access to the real location of the video online, as well as providing access to meta data without having to do a lot of extra work... especially if you're using YouTube videos." 462 | ] 463 | }, 464 | { 465 | "cell_type": "markdown", 466 | "metadata": {}, 467 | "source": [ 468 | "First, create a pafy object with the url that you want to play" 469 | ] 470 | }, 471 | { 472 | "cell_type": "code", 473 | "execution_count": 18, 474 | "metadata": {}, 475 | "outputs": [], 476 | "source": [ 477 | "yt_vid = pafy.new(\"https://www.youtube.com/watch?v=yMBm6cMg-ZA\")" 478 | ] 479 | }, 480 | { 481 | "cell_type": "markdown", 482 | "metadata": {}, 483 | "source": [ 484 | "The pafy object contains all kinds of useful methods and data. I'll let you [check that out in their documentation](https://pythonhosted.org/Pafy/?source=post_page-----cd4574354d8e----------------------#), but I'll show you a few examples here to get you started." 485 | ] 486 | }, 487 | { 488 | "cell_type": "code", 489 | "execution_count": 19, 490 | "metadata": {}, 491 | "outputs": [], 492 | "source": [ 493 | "author = yt_vid.author\n", 494 | "title = yt_vid.title\n", 495 | "desc = yt_vid.description" 496 | ] 497 | }, 498 | { 499 | "cell_type": "code", 500 | "execution_count": 20, 501 | "metadata": {}, 502 | "outputs": [ 503 | { 504 | "name": "stdout", 505 | "output_type": "stream", 506 | "text": [ 507 | "ESPN\n", 508 | "Stephen A. and Max react to Patrick Mahomes taking the Chiefs to the Super Bowl | First Take\n", 509 | "Max Kellerman, Stephen A. Smith and Dan Orlovsky weigh in on Kansas City QB Patrick Mahomes’ success this season and react to the Chiefs heading to the Super Bowl LIV after defeating the San Francisco 49ers 35-24 in the AFC Championship game.\n", 510 | "#FirstTake #NFL\n", 511 | "✔ Subscribe to ESPN+ https://plus.espn.com/\n", 512 | "✔ Get the ESPN App: http://www.espn.com/espn/apps/espn\n", 513 | "✔ Subscribe to ESPN on YouTube: http://es.pn/SUBSCRIBEtoYOUTUBE\n", 514 | "✔ Subscribe to ESPN FC on YouTube: http://bit.ly/SUBSCRIBEtoESPNFC\n", 515 | "✔ Subscribe to NBA on ESPN on YouTube: http://bit.ly/SUBSCRIBEtoNBAonESPN\n", 516 | "✔ Watch ESPN on YouTube TV: http://es.pn/YouTubeTV\n", 517 | "\n", 518 | "Exclusive interviews with Rachel Nichols https://urlzs.com/jNURe\n", 519 | "Stephen A. Smith on ESPN https://urlzs.com/W19Tz\n", 520 | "\n", 521 | "ESPN on Social Media:\n", 522 | "► Follow on Twitter: http://www.twitter.com/espn\n", 523 | "► Like on Facebook: http://www.facebook.com/espn\n", 524 | "► Follow on Instagram: www.instagram.com/f/espn\n", 525 | "\n", 526 | "Visit ESPN on YouTube to get up-to-the-minute sports news coverage, scores, highlights and commentary for NFL, NHL, MLB, NBA, College Football, NCAA Basketball, soccer and more. \n", 527 | "\n", 528 | "More on ESPN.com: https://www.espn.com\n", 529 | "\n" 530 | ] 531 | } 532 | ], 533 | "source": [ 534 | "print(author)\n", 535 | "print(title)\n", 536 | "print(desc)\n", 537 | "print()" 538 | ] 539 | }, 540 | { 541 | "cell_type": "markdown", 542 | "metadata": {}, 543 | "source": [ 544 | "To play the video, you'll need to add the media from the real url provided by the `getbest` method" 545 | ] 546 | }, 547 | { 548 | "cell_type": "code", 549 | "execution_count": 21, 550 | "metadata": {}, 551 | "outputs": [ 552 | { 553 | "data": { 554 | "text/plain": [ 555 | "0" 556 | ] 557 | }, 558 | "execution_count": 21, 559 | "metadata": {}, 560 | "output_type": "execute_result" 561 | } 562 | ], 563 | "source": [ 564 | "# Get the location of the video\n", 565 | "vid_url = yt_vid.getbest().url\n", 566 | "\n", 567 | "# Create a new media instance\n", 568 | "new_vid = instance.media_new(vid_url)\n", 569 | "\n", 570 | "# Add meta data to the video object\n", 571 | "new_vid.set_meta(0, title)\n", 572 | "new_vid.set_meta(1, author)\n", 573 | "new_vid.set_meta(6, desc)\n", 574 | "new_vid.set_meta(10, vid_url)\n", 575 | "\n", 576 | "# Add the video to the media list\n", 577 | "media_list.add_media(new_vid)" 578 | ] 579 | }, 580 | { 581 | "cell_type": "markdown", 582 | "metadata": {}, 583 | "source": [ 584 | "Now that I've added this video to the media_list I can play it by index... since it is the 4th item in the list, I can use the `play_item_at_index` method" 585 | ] 586 | }, 587 | { 588 | "cell_type": "code", 589 | "execution_count": 22, 590 | "metadata": {}, 591 | "outputs": [ 592 | { 593 | "data": { 594 | "text/plain": [ 595 | "0" 596 | ] 597 | }, 598 | "execution_count": 22, 599 | "metadata": {}, 600 | "output_type": "execute_result" 601 | } 602 | ], 603 | "source": [ 604 | "list_player.play_item_at_index(3)" 605 | ] 606 | }, 607 | { 608 | "cell_type": "markdown", 609 | "metadata": {}, 610 | "source": [ 611 | "![](./Tutorial-Assets/vlc4.png)" 612 | ] 613 | }, 614 | { 615 | "cell_type": "code", 616 | "execution_count": 23, 617 | "metadata": {}, 618 | "outputs": [], 619 | "source": [ 620 | "list_player.stop()" 621 | ] 622 | }, 623 | { 624 | "cell_type": "markdown", 625 | "metadata": {}, 626 | "source": [ 627 | "### References\n", 628 | "- [VLC Documentation](https://www.olivieraubert.net/vlc/python-ctypes/doc/)\n", 629 | "- [Pafy Documentation](https://pythonhosted.org/Pafy/?source=post_page-----cd4574354d8e----------------------)" 630 | ] 631 | } 632 | ], 633 | "metadata": { 634 | "kernelspec": { 635 | "display_name": "Python 3", 636 | "language": "python", 637 | "name": "python3" 638 | }, 639 | "language_info": { 640 | "codemirror_mode": { 641 | "name": "ipython", 642 | "version": 3 643 | }, 644 | "file_extension": ".py", 645 | "mimetype": "text/x-python", 646 | "name": "python", 647 | "nbconvert_exporter": "python", 648 | "pygments_lexer": "ipython3", 649 | "version": "3.7.5" 650 | } 651 | }, 652 | "nbformat": 4, 653 | "nbformat_minor": 4 654 | } 655 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VLC Media Player 2 | A media player built with **VLC** & **PySimpleGUI** that will play local and online streaming media. This project is primarily **educational**, so feel free to take and expand it into something much more dynamic and useful. I've also included some very **basic playlist functionality** for convenience. 3 | 4 | ##### Other Resources: 5 | - I've created a [quick-start tutorial](Python-VLC-Quick-Start.ipynb) on how to use the Python-VLC library that covers all of the basic classes and methods used to build this application as well as some others that I haven't included. 6 | 7 | - [Here's a demonstration](https://github.com/oaubert/python-vlc/blob/master/examples/tkvlc.py) that I found online using tkinter; it's a not pretty, but the code was very insightful and much more robust than what you'll find here. 8 | 9 | 10 | ### Getting start with Linux: 11 | Before running this application on Linux, please make sure you install the following libraries and applications. 12 | ``` 13 | sudo pip3 install python-vlc 14 | sudo pip3 install youtube-dl 15 | sudo pip3 install pysimplegui 16 | sudo pip3 install pafy 17 | sudo apt-get install vlc 18 | ``` 19 | 20 | ### Getting start with Windows: 21 | Before running this application on Windows, please make sure you install the following libraries and applications. Additionally, On Windows, you also need to download the VLC version that cooresponds to [32 or 64 bit Windows](https://www.videolan.org/vlc/) 22 | 23 | ``` 24 | pip install python-vlc 25 | pip install pafy 26 | pip install youtube-dl 27 | pip install pysimplegui 28 | ``` 29 | 30 | ### Playlist 31 | I've added some functionality that will allow you to load a playlist via a `playlist.txt` file that must be included in the same directory as the media player script. As you can see from the example image, you can include both local files and url links to streaming media. Spaces between lines do not affect the program, but I didn't take the time to allow for comments, or saving of a new playlist within the application itself. Feel free to add that. 32 | 33 | If you've loaded a playlist, any additional media that you load will be appended to the `media_list` object and will be available to play in the current session, but it does not add the track to the `playlist.txt` file. However, that would be a fairly simple add if someone wanted to do it. 34 | ![](Examples/playlist.png) 35 | 36 | 37 | ### Running on **Windows** 38 | Startup screen 39 | 40 | ![](Examples/startup.PNG) 41 | 42 | Playing media - GREEN indicates item is playing 43 | 44 | ![](Examples/playing1.png) 45 | 46 | Paused media - RED is paused 47 | 48 | ![](Examples/paused.PNG) 49 | 50 | ### Running on **Linux (Raspberry Pi // Rasbian Buster** 51 | 52 | ![](Examples/playing2.PNG) 53 | 54 | 55 | -------------------------------------------------------------------------------- /Tutorial-Assets/vlc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Tutorial-Assets/vlc1.png -------------------------------------------------------------------------------- /Tutorial-Assets/vlc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Tutorial-Assets/vlc2.png -------------------------------------------------------------------------------- /Tutorial-Assets/vlc3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Tutorial-Assets/vlc3.png -------------------------------------------------------------------------------- /Tutorial-Assets/vlc4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/israel-dryer/Media-Player/2bab0f8e3ad3b8bb38e94f46e448c5d6a9387394/Tutorial-Assets/vlc4.png -------------------------------------------------------------------------------- /playlist.txt: -------------------------------------------------------------------------------- 1 | https://www.youtube.com/watch?v=LML6SoNE7xE&t=11s 2 | https://www.youtube.com/watch?v=ZjgvA2bt080 3 | https://www.youtube.com/watch?v=KmeFRKZRfzI 4 | 5 | https://www.youtube.com/watch?v=7ygDk7JFKdI 6 | 7 | C:\Users\us43060\Desktop\Projects\ERA-Studio\Videos\Israel Dryer\install python 3.8.mp4 -------------------------------------------------------------------------------- /vlc_media_player.py: -------------------------------------------------------------------------------- 1 | """ 2 | VLC media player for local and online streaming media 3 | Author: Israel Dryer 4 | Modified: 2020-01-23 5 | 6 | """ 7 | import vlc 8 | import pafy 9 | import PySimpleGUI as sg 10 | from sys import platform as PLATFORM 11 | from os import listdir 12 | 13 | PATH = './Assets/' 14 | BUTTON_DICT = {img[:-4].upper(): PATH + img for img in listdir(PATH)} 15 | DEFAULT_IMG = PATH + 'background2.png' 16 | ICON = PATH + 'player.ico' 17 | 18 | 19 | class MediaPlayer: 20 | 21 | def __init__(self, size, scale=1.0, theme='DarkBlue'): 22 | """ Media player constructor """ 23 | 24 | # Setup media player 25 | self.instance = vlc.Instance() 26 | self.list_player = self.instance.media_list_player_new() 27 | self.media_list = self.instance.media_list_new([]) 28 | self.list_player.set_media_list(self.media_list) 29 | self.player = self.list_player.get_media_player() 30 | 31 | self.track_cnt = 0 # Count of tracks loaded into `media_list` 32 | self.track_num = 0 # Index of the track currently playing 33 | 34 | # Setup GUI window for output of media 35 | self.theme = theme # This can be changed, but I'd stick with a dark theme 36 | self.default_bg_color = sg.LOOK_AND_FEEL_TABLE[self.theme]['BACKGROUND'] 37 | self.window_size = size # The size of the GUI window 38 | self.player_size = [x*scale for x in size] # The size of the media output window 39 | self.window = self.create_window() 40 | self.check_platform() # Window handler adj for Linux/Windows/MacOS 41 | 42 | # Unmute the volume if muted 43 | if self.player.audio_get_mute(): 44 | self.toggle_mute() 45 | 46 | def button(self, key, image, **kwargs): 47 | """ Create media player button """ 48 | return sg.Button(image_filename=image, border_width=0, pad=(0, 0), key=key, 49 | button_color=('white', self.default_bg_color)) 50 | 51 | def create_window(self): 52 | """ Create GUI instance """ 53 | sg.change_look_and_feel(self.theme) 54 | 55 | # Column layout for media player button controls 56 | col1 = [[self.button('SKIP PREVIOUS', BUTTON_DICT['START']), 57 | self.button('PAUSE', BUTTON_DICT['PAUSE_OFF']), 58 | self.button('PLAY', BUTTON_DICT['PLAY_OFF']), 59 | self.button('STOP', BUTTON_DICT['STOP']), 60 | self.button('SKIP NEXT', BUTTON_DICT['END']), 61 | self.button('SOUND', BUTTON_DICT['SOUND_ON']), 62 | self.button('PLAYLIST', BUTTON_DICT['PLAYLIST']), 63 | self.button('PLUS', BUTTON_DICT['PLUS'])]] 64 | 65 | # Column layout for media info and instructions 66 | col2 = [[sg.Text('Open a FILE, STREAM, or PLAYLIST to begin', 67 | size=(45, 3), font=(sg.DEFAULT_FONT, 8), pad=(0, 5), key='INFO')]] 68 | 69 | # Main GUI layout 70 | main_layout = [ 71 | # Media output element -- this is the video output element 72 | [sg.Image(filename=DEFAULT_IMG, pad=(0, 5), size=self.player_size, key='VID_OUT')], 73 | 74 | # Element for tracking elapsed time 75 | [sg.Text('00:00', key='TIME_ELAPSED'), 76 | 77 | # This slide can be used to adjust the playback positon of the video 78 | sg.Slider(range=(0, 1), enable_events=True, resolution=0.0001, disable_number_display=True, 79 | background_color='#83D8F5', orientation='h', key='TIME'), 80 | 81 | # Elements for tracking media length and track counts 82 | sg.Text('00:00', key='TIME_TOTAL'), 83 | sg.Text(' ', key='TRACKS')], 84 | 85 | # Button and media information layout (created above) 86 | [sg.Column(col1), sg.Column(col2)]] 87 | 88 | # Create a PySimpleGUI window from the specified parameters 89 | window = sg.Window('VLC Media Player', main_layout, element_justification='center', icon=ICON, finalize=True) 90 | 91 | # Expand the time element so that the row elements are positioned correctly 92 | window['TIME'].expand(expand_x=True) 93 | return window 94 | 95 | def check_platform(self): 96 | """ Platform specific adjustments for window handler """ 97 | if PLATFORM.startswith('linux'): 98 | self.player.set_xwindow(self.window['VID_OUT'].Widget.winfo_id()) 99 | else: 100 | self.player.set_hwnd(self.window['VID_OUT'].Widget.winfo_id()) 101 | 102 | def add_media(self, track=None): 103 | """ Add new track to list player with meta data if available """ 104 | if track is None: 105 | return # User did not provide any information 106 | 107 | try: # Assume this is an online url and not a file path 108 | vid = pafy.new(track) # Create pafy media object 109 | media = self.instance.media_new(vid.getbest().url) # Get REAL YouTube url 110 | # Set meta data for media if available 111 | media.set_meta(0, vid.title) 112 | media.set_meta(1, vid.author) 113 | 114 | except: # This is a file path and not an online url 115 | media = self.instance.media_new(track) 116 | media.set_meta(0, track.replace('\\', '/').split('/').pop()) # filename 117 | media.set_meta(1, 'Local Media') # Default author value for local media 118 | 119 | finally: 120 | media.set_meta(10, track) # Url if online media else use filename 121 | self.media_list.add_media(media) 122 | self.track_cnt = self.media_list.count() 123 | 124 | # Update infobar with added track 125 | self.window['INFO'].update(f'Loaded: {media.get_meta(0)}') 126 | self.window.read(1) # refresh the screen 127 | 128 | # Update the track counter 129 | if self.track_cnt == 1: 130 | self.track_num = 1 131 | self.list_player.play() 132 | 133 | def get_meta(self, meta_type): 134 | """ Retrieve saved meta data from tracks in media list """ 135 | media = self.player.get_media() 136 | return media.get_meta(meta_type) 137 | 138 | def get_track_info(self): 139 | """ Show author, title, and elasped time if video is loaded and playing """ 140 | time_elapsed = "{:02d}:{:02d}".format(*divmod(self.player.get_time() // 1000, 60)) 141 | time_total = "{:02d}:{:02d}".format(*divmod(self.player.get_length() // 1000, 60)) 142 | if self.player.is_playing(): 143 | message = "{}\n{}".format(self.get_meta(1).upper(), self.get_meta(0)) 144 | self.window['INFO'].update(message) 145 | self.window['TIME_ELAPSED'].update(time_elapsed) 146 | self.window['TIME'].update(self.player.get_position()) 147 | self.window['TIME_TOTAL'].update(time_total) 148 | self.window['TRACKS'].update('{} of {}'.format(self.track_num, self.track_cnt)) 149 | elif self.media_list.count() == 0: 150 | self.window['INFO'].update('Open a FILE, STREAM, or PLAYLIST to begin') 151 | else: 152 | self.window['INFO'].update('Press PLAY to Start') 153 | 154 | def play(self): 155 | """ Called when the play button is pressed """ 156 | self.list_player.play() 157 | self.window['PLAY'].update(image_filename=BUTTON_DICT['PLAY_ON']) 158 | self.window['PAUSE'].update(image_filename=BUTTON_DICT['PAUSE_OFF']) 159 | 160 | def stop(self): 161 | """ Called when the stop button is pressed """ 162 | self.player.stop() 163 | self.window['PLAY'].update(image_filename=BUTTON_DICT['PLAY_OFF']) 164 | self.window['PAUSE'].update(image_filename=BUTTON_DICT['PAUSE_OFF']) 165 | self.window['TIME'].update(value=0) 166 | self.window['TIME_ELAPSED'].update('00:00') 167 | 168 | def pause(self): 169 | """ Called when the pause button is pressed """ 170 | self.window['PAUSE'].update( 171 | image_filename=BUTTON_DICT['PAUSE_ON'] if self.player.is_playing() else BUTTON_DICT['PAUSE_OFF']) 172 | self.window['PLAY'].update( 173 | image_filename=BUTTON_DICT['PLAY_OFF'] if self.player.is_playing() else BUTTON_DICT['PLAY_ON']) 174 | self.player.pause() 175 | 176 | def skip_next(self): 177 | """ Called when the skip next button is pressed """ 178 | self.list_player.next() 179 | self.reset_pause_play() 180 | if not self.track_cnt == self.track_num: 181 | self.track_num += 1 182 | 183 | def skip_previous(self): 184 | """ Called when the skip previous button is pressed """ 185 | self.list_player.previous() 186 | self.reset_pause_play() 187 | if not self.track_num == 1: 188 | self.track_num -= 1 189 | 190 | def reset_pause_play(self): 191 | """ Reset pause play buttons after skipping tracks """ 192 | self.window['PAUSE'].update(image_filename=BUTTON_DICT['PAUSE_OFF']) 193 | self.window['PLAY'].update(image_filename=BUTTON_DICT['PLAY_ON']) 194 | 195 | def toggle_mute(self): 196 | """ Called when the sound button is pressed """ 197 | self.window['SOUND'].update( 198 | image_filename=BUTTON_DICT['SOUND_ON'] if self.player.audio_get_mute() else BUTTON_DICT['SOUND_OFF']) 199 | self.player.audio_set_mute(not self.player.audio_get_mute()) 200 | 201 | def load_single_track(self): 202 | """ Open a popup to request url or filepath for single track """ 203 | track = sg.PopupGetFile('Browse for local media or enter URL:', 204 | title='Load Media') 205 | if track is None: 206 | return 207 | else: 208 | self.window['INFO'].update('Loading media...') 209 | self.window.read(1) 210 | self.add_media(track) 211 | if self.media_list.count() > 0: 212 | self.play() 213 | 214 | def load_playlist_from_file(self): 215 | """ Open text file and load to new media list. Assumes `playlist.txt` is in root directory """ 216 | 217 | # Read contents of playlist file 218 | with open('./playlist.txt', 'r') as f: 219 | try: 220 | playlist = f.read().splitlines() 221 | except IOError: 222 | sg.popup_error('There was an error opening the file. Please make sure `playlist.txt` is in\ 223 | the same path as the media player executable', title='Playlist Error') 224 | return 225 | 226 | # Send message to window and update 227 | self.window['INFO'].update('Loading playlist...') 228 | self.window.read(1) 229 | 230 | # Add each track to the playlist if a valid url or file path 231 | for track in playlist: 232 | if track.strip(): 233 | self.add_media(track.replace('\\', '/')) 234 | 235 | # Play this track if player not playing (allows for faster perceived startup) 236 | if not self.player.is_playing: 237 | self.play() 238 | 239 | # Increment the track counter 240 | self.track_cnt = self.media_list.count() 241 | self.track_num = 1 242 | 243 | 244 | def main(): 245 | """ The main program function """ 246 | 247 | # Create the media player 248 | mp = MediaPlayer(size=(1920, 1080), scale=0.5) 249 | 250 | # Main event loop 251 | while True: 252 | event, values = mp.window.read(timeout=1000) 253 | mp.get_track_info() 254 | if event in (None, 'Exit'): 255 | break 256 | if event == 'PLAY': 257 | mp.play() 258 | if event == 'PAUSE': 259 | mp.pause() 260 | if event == 'SKIP NEXT': 261 | mp.skip_next() 262 | if event == 'SKIP PREVIOUS': 263 | mp.skip_previous() 264 | if event == 'STOP': 265 | mp.stop() 266 | if event == 'SOUND': 267 | mp.toggle_mute() 268 | if event == 'TIME': 269 | mp.player.set_position(values['TIME']) 270 | if event == 'PLUS': 271 | mp.load_single_track() 272 | if event == 'PLAYLIST': 273 | mp.load_playlist_from_file() 274 | 275 | 276 | if __name__ == '__main__': 277 | main() --------------------------------------------------------------------------------