Introduction: Retro Idiot Box
I found an old Magnavox portable television from 1984 sitting on a shelf at my local thrift store. I thought to myself, "oh NEAT!" Upon further inspection I noticed a $15 price tag on it, so I decided to take it home and make something spiffy out of it. I remember watching reruns as a kid of all the great classics in glorious black and white, and wanted to make this a reality again.
The problem is that there are no analog stations out there anymore, and this is entirely incapable of doing any ATSC decoding, or any digital decoding. I did notice the presence of an AV connection on the side, and had a few raspberry pi's laying around, so I decided to embark on an adventure to figure out how I could stream channels to this. I want to make it look sharp, too. I won't be running it on the 9 D-cell batteries, so I can hide the rpi in the battery compartment with a host of other goodies.
Step 1: Get a Good IPTV List
Step 2: Preliminary Code Experiment
The python code that we are going to write parses the m3u file into a list of stations.
#!/usr/bin/python3 import subprocess from sys import argv class Station: def __init__(self): self.channel = 0 self.name = '' self.address = '' channel_list =  with open('./us-m3uplaylist-2020-08-17-1.m3u', 'r') as m3u: i = 0 for line in m3u: if line.startswith('#EXTINF'): this = Station() this.name = line.split(',') line = next(m3u) this.address = line.strip() this.channel = i channel_list.append(this) i = i + 1 process = subprocess.Popen(['vlc', '--loop', '--intf', 'dummy', '--fullscreen', channel_list[int(argv)].address])
Let's break this down.
This tells bash that we will be using python3 to interpret this file.
import subprocess<br>from sys import argv
We will need the subprocess module to launch our vlc instance, and we will need argv to choose which channel we will launch vlc into.
class Station:<br> def __init__(self): self.channel = 0 self.name = '' self.address = ''
This defines a class called Station. Each channel will have a channel number, the name of the channel as taken from the m3u file, and an address of where that channel is streaming from.
channel_list = 
This is a list that will store all of the channels parsed from the m3u file.
with open('./us-m3uplaylist-2020-08-17-1.m3u', 'r') as m3u:<br> i = 0 for line in m3u: if line.startswith('#EXTINF'): this = Station() this.name = line.split(',') line = next(m3u) this.address = line.strip() this.channel = i channel_list.append(this) i = i + 1
This loop opens the m3u playlist, and ingests the data. the m3u file lines that we are interested in starts with #EXTINF, This indicates a new entry to the playlist file. The next value of interest is the name, which is on the same line as #EXTINF, but with a comma between them. The following line of this particular m3u is the address of the stream. There is an iterator "i" in use to count which channel is which. This loop iterates through the entire m3u file and fills the channel_list with stations.
process = subprocess.Popen(['vlc', '--loop', '--intf', 'dummy', '--fullscreen', channel_list[int(argv)].address])
the subprocess library allows python to call processes (programs) and returns a PID (Process ID). This enables python to be able to manage launching and closing of programs "correctly" without filling up the history file, or allowing more arbitrary code to be run with generic "system" calls. Each element of the array used as an argument to Popen is as typed in the command line.
vlc --loop --intf dummy --fullscreen addresss
The above command is what is desired to be ran, with the --loop option fixing some issues with stream pausing while next chunks load (weird m3u8 issues), --intf dummy starts vlc without the interface, just a screen, --fullscreen launches the video in fullscreen mode (NO WAY!), and the address is the address of the stream. As you can see in the code, we are providing the address from the list's channel number, which is provided at runtime via the argv statement. Save this file as tv_channels.py, change the playlist location in the python file to point to your playlist, and you can run the code as follows:
python tv_channels.py <channel number>
Step 3: Add GPIO
The schematic shows the two GPIO pins in use for the buttons, and each one has a pull up resistor to keep the GPIO pin pulled high after the button press. The code previously defined can be refined to make the operation a little more seamless by adding GPIO capability. This allows us to change the channel with buttons, rather than a keyboard and argv statements, just like a for realsies tv.
The first thing of note is that I have the television defined as a class. To be a television, we need to be on a current channel, have a list of possible channels, and have the ability to change the channels. In this example, the only method of changing channels will be to move up the channel list, and move down the channel list. Once the channel is decided, we will have to start VLC on the channel we want to see.
#!/usr/bin/python3 from time import sleep import subprocess from sys import argv from gpiozero import Button class Station: def __init__(self): self.channel = 0 self.name = '' self.address = '' self.process = '' class Television: def __init__(self, filename): self.current_channel = 0 self.channel_list =  self.build_channel_list(filename) self.start_channel() def build_channel_list(self, filename): with open(filename, 'r') as m3u: i = 0 for line in m3u: if line.startswith('#EXTINF'): this = Station() this.name = line.split(',') line = next(m3u) this.address = line.strip() this.channel = i self.channel_list.append(this) i = i + 1 def channel_up(self): self.current_channel = self.current_channel + 1 if self.current_channel > len(self.channel_list): self.current_channel = len(self.channel_list) self.start_channel() def channel_down(self): self.current_channel = self.current_channel - 1 if self.current_channel < 0: self.current_channel = 0 self.start_channel() def start_channel(self): try: self.process.kill() except: pass print('starting channel %d' % self.current_channel) self.process = subprocess.Popen(['vlc', '-q', '--loop', '--intf', 'dummy', '--fullscreen', self.channel_list[self.current_channel].address]) this = Television('./us-m3uplaylist-2020-08-17-1.m3u') channel_UP = Button(18) channel_DN = Button(23) while True: channel_UP.when_pressed = this.channel_up channel_DN.when_pressed = this.channel_down
This iteration of code has quite a few improvements. it now utilized a module called gpiozero that is required by the raspberry pi to easily access functionality of the GPIO pins
sudo apt-get install python3-gpiozero
sudo pip install gpiozero
As seen in my code, I have chosen GPIO 18 and GPIO 23 for channel UP and channel DOWN respectively. The gpiozero library has a nice class for buttons functions for when_pressed, is_pressed, when_held, etc. This makes it pretty easy. I chose the when_pressed, which refers to a callback function to run when this signal is detected.
The last major change is the inclusion of the ' -q ' option in the VLC subprocess call. This simply runs vlc without all the output to the terminal to keep it free of clutter so that we can see the informational print statements in the code.
Step 4: Integrate the Hardware to Look Sharp
I haven't figured out how I want to accomplish this, and it will be a unique solution for each model tv in use. I need to think hard about this and probe around the television to find a good power source for the pi once I cram the computer inside the massive battery compartment. I've also considered using the clock buttons for the channel selection, as they are already beautifully placed on the television, and the clock doesn't work anyway. I'll post more when I find a good solution, but this is where My project will greatly diverge from everyone else's. Enjoy the real-tv-like IPTV integration!
Step 5: Pi Power
For the model TV I found, it requires a 12V power supply. I probed around the board, but didn't see any obvious power regulators for 5V, so the most obvious place to get a steady power supply is on the circuit board where the barrel connector for the 12V comes in. There's an obvious problem with this. we don't want to fry the pi, so we will need a power regulator. I have chosen the MP2315 Step-Down Power Converter. It's dirt cheap, and simple to use. We will solder the 12VDC input from the barrel connector on the PCB to the IN+ and GND pins of the converter, and the VO+ to pin 2 on the Raspberry Pi, as well as a GND.
BEFORE this is done, be sure to power up the converter and ensure that the proper 5V is coming out of the output. I chose the simplest option with the hardwired adjustable voltage. The trimmer will adjust the voltage, so I watched the voltage output with a multimeter as I adjusted the trimmer with a screwdriver.
Step 6: Integrating Power
After digging around the television, it was decided that the best place to grab out power was from the negative of the barrel connector and the television ON/OFF switch, which means that we can turn on and off the streams with the television, rather that constantly powering the pi by pulling directly from the barrel connector.
The wires were soldered in and fed along the side of the PCB's next to the case until they reached the back of the unit, where they were fed through a hole that was in the back of the battery compartment. Once they were fed through, we can prep the ends of the cabling and solder them down to the power regulator. I tuned it for 5V to power the pi and soldered header pins to it so that we can run female to female jumpers from the power regulator directly to the pi's GPIO header set. This is normally not advised, as the pi generally gets power through the UBS, which has a regulator inline to condition the 5V, but since the power is already being regulated it should be fine.
There is some noise on the audio lines from doing this, because there's a ground loop in the system. I tried many power and ground points all over the board hoping for an easy answer, but didn't find any. I also soldered an microUSB cable to the switched mode regulator to see if forcing power through the pi's internal regulators would sort the issue. It didn't. The solution is going to be in some audio ground isolation transformers. These were ordered rather than built, because they are cheap and nicely packaged. You can pick them up from most auto audio stores or departments. This is what I chose.
Step 7: Long Term Button Solution
Doubtlessly, the buttons won't stay on a breadboard, so there needs to be a more permanent solution. I grabbed some old protoboard and threw the circuit together with some header pins to make it easy to access the signals. This is where everyone will have a difference of opinion on how to attach or mount the buttons. I am choosing to protoboard them and just attach them to the chassis such that the handle that swings over the screen to carry does not interfere. Feel free to church up the design by adding a 3d printed case that smooths mounting, use nuts and bolts, fancy adhesives, integrate original buttons, whatever. As long as it works, there aren't any wrong answers.
These will be mounted to the outside of the case, and the Raspberry Pi will be tucked inside the very roomy battery compartment, so there will need to be a small hole drilled to allow cables to egress the battery compartment.
Step 8: Final Fit Check
All of the equipment needs to be fit checked one last time to see exactly where all of the holes need to be made to the chassis, and what size holes, etc need to be made. Additionally it should be considered where to place the components for optimal ease of connectivity and access. Long story short, make sure everything fits where you think it does before you irreparably damage your project and have to get the spackle out.
Step 9: Final Integration
Now all of the hardware is where it needs to be, and it all fits just as snug as a bug in a rug. Let's cut things! I identified a place on the battery compartment where I could route the AV cables out using a small indentation in the plastic. I ground it down with a bench grinder. It made pretty short work of it. I used a dremel to grind down more plastic to make it a pretty good fit for the cables.
The last component is the channel selector. I drilled a small hole in the battery compartment and routed the header cables out of it one at a time. The buttons were connected up, and I attached the protoboard to the plastic chassis with two halves of pre-adhesive'ed velcro. I get that there was about 1200 better ways to do this, but this worked, and I had everything I needed on hand.
Step 10: Enjoy Your Vintage IPTV
That about sums it up. Find shows and have fun watching. Don't sit too close, though. You'll rot your brain!
There's a lot of room to improve this project, so take it in whatever direction you want, but it was fun getting this far. As for me, I run this from a cronjob at reboot, so the stdout doesn't capture the messages from the python script. I would like to fix this so I know what channel I'm on. Another good addition is a wireless keyboard dongle on the Pi. This would let you change the wifi network if you leave your house with the tv. Regardless. it was a fun project, and I can't wait to get started on the next one.