Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
![Example of Ice](ice-example.png "Example")
![Example of Ice using thegamesdb.net boxart images](ice-example-new.png "Example")

##Ice
##Ice (TheGamesDB.net Addon)

###Description

The purpose of this project is to leverage Steam's Big Picture mode to turn it into an emulator frontend (similar to Hyperspin). It accomplishes this by creating folders in specified locations on the user's hard drive, then adding any ROMs that are placed in these folders to Steam as non-Steam games. Emulators are installed and configured by the user before Ice is run.
This fork adds a new provider to download images from thegamesdb.net
It also generates nicer coverimages if the coverimage doesnt fit steam banner size.

Have fun with it.

###License

Expand All @@ -24,6 +27,3 @@ Next, you will need to download all of Ice's dependencies. To do so, run `python

Once all of that is finished, simply run `python -m ice` from the repository's root directory.

###Ice GUI

A GUI for Ice is currently being developed, but is very far from being production ready. As of writing, it is basically non-functional. Do not attempt to use the GUI, doing so will only bring you pain and heartache.
Binary file added bin/Ice-TheGamesDB.zip
Binary file not shown.
10 changes: 6 additions & 4 deletions config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ Userdata Directory=
# a game of the same name.
# * `consolegrid` - Hits the ConsoleGrid API looking for a game of that name.
#
# * `thegamesdb` - Hits the TheGamesDB API looking for a game of that name.
#
# Ice will ask these providers for images in order, stopping when it one is
# found. This means order matters, and `local, consolegrid` is not the same as
# `consolegrid, local`.
# found. This means order matters, and `local, consolegrid, thegamesdb` is not the same as
# `consolegrid, local, thegamesdb`.
#
# The default is `local, consolegrid`
Providers=local, consolegrid
# The default is `local, consolegrid, thegamesdb`
Providers=local, thegamesdb
Binary file added ice-example-new.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions ice/gridproviders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
"consolegrid_provider",
"grid_image_provider",
"local_provider",
"thegamesdb_provider",
]
134 changes: 134 additions & 0 deletions ice/gridproviders/thegamesdb_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env python
# encoding: utf-8
"""
thegamesdb_provider.py
Created by Wolfgang Bergbauer on 2018-02-09.
Copyright (c) 2018 Wolfgang Bergbauer. All rights reserved.
"""
import requests

import xml.etree.ElementTree

import shutil

import grid_image_provider

from ice.logs import logger
from ice.steam_banner_creator import SteamBannerCreator


class TheGamesDBProvider(grid_image_provider.GridImageProvider):

@staticmethod
def user_agent():
return "Mozilla/5.0"

@staticmethod
def api_url_findId():
return "http://thegamesdb.net/api/GetGamesList.php?name="

@staticmethod
def api_url_getArt():
return "http://thegamesdb.net/api/GetArt.php?id="

def http_get(self, url):
return requests.get(url, {'User-agent': self.user_agent()})

def searchTheGamesDb(self, rom):
url = self.api_url_findId() + str(rom.name)
try:
response = self.http_get(url)
body = response.content
gameList = xml.etree.ElementTree.fromstring(body)
foundImage = self.findImageForPlattformGame(gameList, rom)
return foundImage
except IOError as error:
logger.debug(
"There was an error contacting %s" % url
)

def getFileType(self, url):
return url[-4:]

def download_image(self, url):

filetype = self.getFileType(url)
logger.debug(
"Downloading %s \n Extrated filetype: %s " % (url, filetype)
)
if filetype is not None and len(filetype) > 3:

from tempfile import mkstemp
fd, path = mkstemp(suffix=filetype)
response = requests.get(url, stream=True)
with open(path, 'wb') as out_file:
shutil.copyfileobj(response.raw, out_file)
del response
return path

def findArtWorkUrl(self, gameId):
url = self.api_url_getArt() + str(gameId)
try:
response = self.http_get(url)
body = response.content
xmlResult = xml.etree.ElementTree.fromstring(body)
baseImgUrl = xmlResult._children[0].text
imagesXml = xmlResult._children[1]
imgUrl = None

# try to find a fanart
boxarts = imagesXml.findall("boxart")
frontBoxArt = self.findFrontBoxArt(boxarts)

if frontBoxArt is not None:
imgUrl = frontBoxArt.text
else:
fanart = imagesXml.find("fanart")
if fanart is not None:
imgUrl = fanart.text
else:
if len(imagesXml._children) > 0:
# take any image which is available
imgUrl = imagesXml._children[0].text

if imgUrl is not None:
return baseImgUrl + imgUrl
else:
return None
except IOError as error:
logger.debug(
"There was an error contacting %s" % url
)

def image_for_rom(self, rom):
imgUrl = self.searchTheGamesDb(rom)
if imgUrl is not None:
img = self.download_image(imgUrl)
SteamBannerCreator().convertToSteamBannerImage(img)
return img


def findFrontBoxArt(self, boxarts):
if boxarts is not None:
for boxart in boxarts:
if boxart.get('side') == "front":
return boxart

def findImageForPlattformGame(self, gameList, rom):
romConsole = rom.console.fullname
foundgame = None
foundImage = None
for game in gameList:
gameConsole = game.find('Platform').text
if gameConsole == romConsole or gameConsole in romConsole or romConsole in gameConsole:
foundImage = self.findArtWorkUrl(game.find('id').text)
if foundImage is not None:
foundgame = game
break

# If no suitable console was found, take the first one
if foundgame is None and len(gameList._children) > 0:
foundgame = gameList._children[0]
foundImage = self.findArtWorkUrl(foundgame.find('id').text)

return foundImage
2 changes: 2 additions & 0 deletions ice/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import configuration
import model
import paths
from gridproviders.thegamesdb_provider import TheGamesDBProvider

from logs import logger
from gridproviders.combined_provider import CombinedProvider
Expand Down Expand Up @@ -73,6 +74,7 @@ def image_provider(config):
providerByName = {
"local": LocalProvider,
"consolegrid": ConsoleGridProvider,
"thegamesdb": TheGamesDBProvider,
}
normalize = lambda s: s.strip().lower()
names = map(normalize, config.provider_spec.split(","))
Expand Down
46 changes: 46 additions & 0 deletions ice/steam_banner_creator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# encoding: utf-8
"""
steam_banner_creator.py

Created by Wolfgang on 2018-02-10.
Copyright (c) 2018 Wolfgang Bergbauer. All rights reserved.

This class creates a steam banner image from an given image.
"""
from PIL import Image, ImageFilter

from ice.logs import logger


class SteamBannerCreator():

@staticmethod
def STEAM_BANNER_HEIGHT():
return 215

@staticmethod
def STEAM_BANNER_SIZE(): return 460, 215

def convertToSteamBannerImage(self, oldImagePath):
try:
background = self.__createGaussianBackground(oldImagePath)
foreground = self.resizeToHeight(self.STEAM_BANNER_HEIGHT(), oldImagePath)
background.paste(foreground, (background.size[0]/2 - foreground.size[0] / 2, 0))
background.save(oldImagePath)
except IOError as error:
logger.debug(
"There was an error converting the image " + oldImagePath + ": " + error.message
)

def __createGaussianBackground(self, oldImagePath):
background = Image.open(oldImagePath)
background = background.resize(self.STEAM_BANNER_SIZE(), Image.ANTIALIAS)
background = background.filter(ImageFilter.GaussianBlur(radius=20))
return background

def resizeToHeight(self, height, oldImagePath):
img = Image.open(oldImagePath)
hpercent = (height / float(img.size[1]))
wsize = int((float(img.size[0]) * float(hpercent)))
img = img.resize((wsize, height), Image.ANTIALIAS)
return img
31 changes: 31 additions & 0 deletions tests/steam_banner_creator_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# encoding: utf-8
"""
steam_banner_creator_tests.py

Created by Wolfgang on 2018-02-10.
Copyright (c) 2018 Wolfgang Bergbauer. All rights reserved.
"""

import unittest

import os
from mockito import *

# I need to do this instead of importing the class explicitly so that I can
# override the urllib2 function.
# TODO: Use dependency injection so I don't need to use that hack.
from ice.gridproviders import thegamesdb_provider
from ice.steam_banner_creator import SteamBannerCreator


class SteamBannerCreatorTests(unittest.TestCase):

def test_convertAll(self):
folder = "D:\\Steam\\userdata\\17910320\\config\\grid\\"
converter = SteamBannerCreator()
for filename in os.listdir(folder):
converter.convertToSteamBannerImage(folder + filename)

def test_createImage(self):
testImagePath = "C:\\Users\\bergb\\test.jpg"
SteamBannerCreator().convertToSteamBannerImage(testImagePath)
51 changes: 51 additions & 0 deletions tests/thegamesdb_provider_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# encoding: utf-8
"""
consolegrid_provider_tests.py

Created by Wolfgang on 2018-02-09.
Copyright (c) 2018 Wolfgang Bergbauer. All rights reserved.
"""

import os
import unittest
from mockito import *
from urllib2 import URLError

# I need to do this instead of importing the class explicitly so that I can
# override the urllib2 function.
# TODO: Use dependency injection so I don't need to use that hack.
from ice.gridproviders import thegamesdb_provider


class TheGamesDBProviderTests(unittest.TestCase):

def setUp(self):
self.provider = thegamesdb_provider.TheGamesDBProvider()

def tearDown(self):
pass

def create_mock_rom(self, rom_name="Test ROM", console_name="Test"):
console = mock()
console.fullname = console_name
console.shortname = console_name

rom = mock()
rom.name = rom_name
rom.console = console
return rom

def test_findId(self):
rom = self.create_mock_rom("Mega Man")
id = self.provider.findGamesDBId(rom)
self.assertIsNotNone(id)

def test_findArt(self):
testid = 1093
url = self.provider.findArtWorkUrl(testid)
self.assertIsNotNone(url)

def test_imageForRom(self):
rom = self.create_mock_rom("Mega Man")
image = self.provider.image_for_rom(rom)
self.assertIsNotNone(image)